/* * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application * Copyright (C) 2010, Mathieu Parent * * 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 * Mathieu Parent * Portions created by the Initial Developer are Copyright (C) * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Mathieu Parent * * * mod_skinny.c -- Skinny Call Control Protocol (SCCP) Endpoint Module * */ #include #include "mod_skinny.h" #include "skinny_protocol.h" SWITCH_MODULE_LOAD_FUNCTION(mod_skinny_load); SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_skinny_shutdown); SWITCH_MODULE_RUNTIME_FUNCTION(mod_skinny_runtime); SWITCH_MODULE_DEFINITION(mod_skinny, mod_skinny_load, mod_skinny_shutdown, mod_skinny_runtime); switch_endpoint_interface_t *skinny_endpoint_interface; static switch_memory_pool_t *module_pool = NULL; skinny_globals_t globals; SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_codec_string, globals.codec_string); SWITCH_DECLARE_GLOBAL_STRING_FUNC(set_global_codec_rates_string, globals.codec_rates_string); /*****************************************************************************/ /* SQL TABLES */ /*****************************************************************************/ static char devices_sql[] = "CREATE TABLE skinny_devices (\n" " name VARCHAR(16),\n" " user_id INTEGER,\n" " instance INTEGER,\n" " ip VARCHAR(255),\n" " type INTEGER,\n" " max_streams INTEGER,\n" " port INTEGER,\n" " codec_string VARCHAR(255)\n" ");\n"; static char buttons_sql[] = "CREATE TABLE skinny_buttons (\n" " device_name VARCHAR(16),\n" " position INTEGER,\n" " type VARCHAR(10),\n" " label VARCHAR(40),\n" " value VARCHAR(24),\n" " settings VARCHAR(44)\n" ");\n"; /*****************************************************************************/ /* PROFILES FUNCTIONS */ /*****************************************************************************/ static switch_status_t dump_profile(const skinny_profile_t *profile, switch_stream_handle_t *stream) { const char *line = "================================================================================================="; switch_assert(profile); stream->write_function(stream, "%s\n", line); /* prefs */ stream->write_function(stream, "Name \t%s\n", profile->name); stream->write_function(stream, "Domain Name \t%s\n", profile->domain); stream->write_function(stream, "IP \t%s\n", profile->ip); stream->write_function(stream, "Port \t%d\n", profile->port); stream->write_function(stream, "Dialplan \t%s\n", profile->dialplan); stream->write_function(stream, "Context \t%s\n", profile->context); stream->write_function(stream, "Keep-Alive \t%d\n", profile->keep_alive); stream->write_function(stream, "Date-Format \t%s\n", profile->date_format); /* db */ stream->write_function(stream, "DBName \t%s\n", profile->dbname ? profile->dbname : switch_str_nil(profile->odbc_dsn)); /* stats */ stream->write_function(stream, "CALLS-IN \t%d\n", profile->ib_calls); stream->write_function(stream, "FAILED-CALLS-IN \t%d\n", profile->ib_failed_calls); stream->write_function(stream, "CALLS-OUT \t%d\n", profile->ob_calls); stream->write_function(stream, "FAILED-CALLS-OUT \t%d\n", profile->ob_failed_calls); /* listener */ stream->write_function(stream, "Listener-Threads \t%d\n", profile->listener_threads); stream->write_function(stream, "%s\n", line); return SWITCH_STATUS_SUCCESS; } static skinny_profile_t *skinny_find_profile(const char *profile_name) { return (skinny_profile_t *) switch_core_hash_find(globals.profile_hash, profile_name); } static switch_status_t skinny_profile_find_listener_by_device_name(skinny_profile_t *profile, const char *device_name, listener_t **listener) { switch_mutex_lock(profile->listener_mutex); for (listener_t *l = profile->listeners; l; l = l->next) { if (!strcmp(l->device_name, device_name)) { *listener = l; } } switch_mutex_unlock(profile->listener_mutex); return SWITCH_STATUS_SUCCESS; } struct skinny_profile_find_listener_helper { skinny_profile_t *profile; listener_t *listener; uint32_t line; }; static int skinny_profile_find_listener_callback(void *pArg, int argc, char **argv, char **columnNames) { struct skinny_profile_find_listener_helper *helper = pArg; skinny_profile_t *profile = helper->profile; char *device_name = argv[0]; /* uint32_t position = atoi(argv[1]); */ uint32_t relative_position = atoi(argv[2]); skinny_profile_find_listener_by_device_name(profile, device_name, &helper->listener); if(helper->listener) { helper->line = relative_position; } return 0; } static switch_status_t skinny_profile_find_listener_by_dest(skinny_profile_t *profile, const char *dest, listener_t **l, uint32_t *line) { char *sql; struct skinny_profile_find_listener_helper helper = {0}; helper.profile = profile; if ((sql = switch_mprintf("SELECT device_name, position, " "(SELECT count(*) from skinny_buttons sb2 " "WHERE sb2.device_name= sb1.device_name AND sb2.type='line' AND sb2.position <= sb1.position) AS relative_position " "FROM skinny_buttons sb1 WHERE type='line' and value='%s'", dest))) { skinny_execute_sql_callback(profile, profile->listener_mutex, sql, skinny_profile_find_listener_callback, &helper); switch_safe_free(sql); } *line = helper.line; *l = helper.listener; return SWITCH_STATUS_SUCCESS; } /*****************************************************************************/ /* SQL FUNCTIONS */ /*****************************************************************************/ void skinny_execute_sql(skinny_profile_t *profile, char *sql, switch_mutex_t *mutex) { switch_core_db_t *db; if (mutex) { switch_mutex_lock(mutex); } if (switch_odbc_available() && profile->odbc_dsn) { switch_odbc_statement_handle_t stmt; if (switch_odbc_handle_exec(profile->master_odbc, sql, &stmt, NULL) != SWITCH_ODBC_SUCCESS) { char *err_str; err_str = switch_odbc_handle_get_error(profile->master_odbc, stmt); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "ERR: [%s]\n[%s]\n", sql, switch_str_nil(err_str)); switch_safe_free(err_str); } switch_odbc_statement_handle_free(&stmt); } else { if (!(db = switch_core_db_open_file(profile->dbname))) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Opening DB %s\n", profile->dbname); goto end; } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SQL: %s\n", sql); switch_core_db_persistant_execute(db, sql, 1); switch_core_db_close(db); } end: if (mutex) { switch_mutex_unlock(mutex); } } switch_bool_t skinny_execute_sql_callback(skinny_profile_t *profile, switch_mutex_t *mutex, char *sql, switch_core_db_callback_func_t callback, void *pdata) { switch_bool_t ret = SWITCH_FALSE; switch_core_db_t *db; char *errmsg = NULL; if (mutex) { switch_mutex_lock(mutex); } if (switch_odbc_available() && profile->odbc_dsn) { switch_odbc_handle_callback_exec(profile->master_odbc, sql, callback, pdata, NULL); } else { if (!(db = switch_core_db_open_file(profile->dbname))) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Opening DB %s\n", profile->dbname); goto end; } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "SQL: %s\n", sql); switch_core_db_exec(db, sql, callback, pdata, &errmsg); if (errmsg) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SQL ERR: [%s] %s\n", sql, errmsg); free(errmsg); } if (db) { switch_core_db_close(db); } } end: if (mutex) { switch_mutex_unlock(mutex); } return ret; } /*****************************************************************************/ /* CHANNEL FUNCTIONS */ /*****************************************************************************/ uint32_t skinny_line_perform_set_state(listener_t *listener, const char *file, const char *func, int line, uint32_t instance, uint32_t state, uint32_t call_id) { switch_assert(listener); switch_log_printf(SWITCH_CHANNEL_ID_LOG, file, func, line, NULL, SWITCH_LOG_DEBUG, "Device %s, line %d State Change %s (%d) -> %s (%d) (no session)\n", listener->device_name, instance, skinny_soft_key_set2str(listener->line_state[instance]), listener->line_state[instance], skinny_soft_key_set2str(state), state); send_select_soft_keys(listener, instance, call_id, state, 0xffff); listener->line_state[instance] = state; return listener->line_state[instance]; } uint32_t skinny_line_get_state(listener_t *listener, uint32_t instance) { switch_assert(listener); return listener->line_state[instance]; } switch_status_t skinny_tech_set_codec(private_t *tech_pvt, int force) { int ms; switch_status_t status = SWITCH_STATUS_SUCCESS; int resetting = 0; if (!tech_pvt->iananame) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(tech_pvt->session), SWITCH_LOG_ERROR, "No audio codec available\n"); switch_goto_status(SWITCH_STATUS_FALSE, end); } if (switch_core_codec_ready(&tech_pvt->read_codec)) { if (!force) { switch_goto_status(SWITCH_STATUS_SUCCESS, end); } if (strcasecmp(tech_pvt->read_impl.iananame, tech_pvt->iananame) || tech_pvt->read_impl.samples_per_second != tech_pvt->rm_rate || tech_pvt->codec_ms != (uint32_t)tech_pvt->read_impl.microseconds_per_packet / 1000) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(tech_pvt->session), SWITCH_LOG_DEBUG, "Changing Codec from %s@%dms to %s@%dms\n", tech_pvt->read_impl.iananame, tech_pvt->read_impl.microseconds_per_packet / 1000, tech_pvt->rm_encoding, tech_pvt->codec_ms); switch_core_session_lock_codec_write(tech_pvt->session); switch_core_session_lock_codec_read(tech_pvt->session); resetting = 1; switch_core_codec_destroy(&tech_pvt->read_codec); switch_core_codec_destroy(&tech_pvt->write_codec); } else { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(tech_pvt->session), SWITCH_LOG_DEBUG, "Already using %s\n", tech_pvt->read_impl.iananame); switch_goto_status(SWITCH_STATUS_SUCCESS, end); } } if (switch_core_codec_init(&tech_pvt->read_codec, tech_pvt->iananame, tech_pvt->rm_fmtp, tech_pvt->rm_rate, tech_pvt->codec_ms, 1, SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE | 0 /* TODO tech_pvt->profile->codec_flags */, NULL, switch_core_session_get_pool(tech_pvt->session)) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(tech_pvt->session), SWITCH_LOG_ERROR, "Can't load codec?\n"); switch_goto_status(SWITCH_STATUS_FALSE, end); } if (switch_core_codec_init(&tech_pvt->write_codec, tech_pvt->iananame, tech_pvt->rm_fmtp, tech_pvt->rm_rate, tech_pvt->codec_ms, 1, SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE | 0 /* TODO tech_pvt->profile->codec_flags */, NULL, switch_core_session_get_pool(tech_pvt->session)) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(tech_pvt->session), SWITCH_LOG_ERROR, "Can't load codec?\n"); switch_goto_status(SWITCH_STATUS_FALSE, end); } switch_assert(tech_pvt->read_codec.implementation); switch_assert(tech_pvt->write_codec.implementation); tech_pvt->read_impl = *tech_pvt->read_codec.implementation; tech_pvt->write_impl = *tech_pvt->write_codec.implementation; switch_core_session_set_read_impl(tech_pvt->session, tech_pvt->read_codec.implementation); switch_core_session_set_write_impl(tech_pvt->session, tech_pvt->write_codec.implementation); if (switch_rtp_ready(tech_pvt->rtp_session)) { switch_assert(tech_pvt->read_codec.implementation); if (switch_rtp_change_interval(tech_pvt->rtp_session, tech_pvt->read_impl.microseconds_per_packet, tech_pvt->read_impl.samples_per_packet ) != SWITCH_STATUS_SUCCESS) { switch_channel_t *channel = NULL; channel = switch_core_session_get_channel(tech_pvt->session); assert(channel != NULL); switch_channel_hangup(channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER); switch_goto_status(SWITCH_STATUS_FALSE, end); } } tech_pvt->read_frame.rate = tech_pvt->rm_rate; ms = tech_pvt->write_codec.implementation->microseconds_per_packet / 1000; if (!switch_core_codec_ready(&tech_pvt->read_codec)) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(tech_pvt->session), SWITCH_LOG_ERROR, "Can't load codec?\n"); switch_goto_status(SWITCH_STATUS_FALSE, end); } switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(tech_pvt->session), SWITCH_LOG_DEBUG, "Set Codec %s %s/%ld %d ms %d samples\n", "" /* TODO switch_channel_get_name(tech_pvt->channel)*/, tech_pvt->iananame, tech_pvt->rm_rate, tech_pvt->codec_ms, tech_pvt->read_impl.samples_per_packet); tech_pvt->read_frame.codec = &tech_pvt->read_codec; tech_pvt->write_codec.agreed_pt = tech_pvt->agreed_pt; tech_pvt->read_codec.agreed_pt = tech_pvt->agreed_pt; if (force != 2) { switch_core_session_set_read_codec(tech_pvt->session, &tech_pvt->read_codec); switch_core_session_set_write_codec(tech_pvt->session, &tech_pvt->write_codec); } /* TODO tech_pvt->fmtp_out = switch_core_session_strdup(tech_pvt->session, tech_pvt->write_codec.fmtp_out); */ /* TODO if (switch_rtp_ready(tech_pvt->rtp_session)) { switch_rtp_set_default_payload(tech_pvt->rtp_session, tech_pvt->pt); } */ end: if (resetting) { switch_core_session_unlock_codec_write(tech_pvt->session); switch_core_session_unlock_codec_read(tech_pvt->session); } return status; } void tech_init(private_t *tech_pvt, switch_core_session_t *session, listener_t *listener, uint32_t line) { struct line_stat_res_message *button = NULL; switch_assert(tech_pvt); switch_assert(session); switch_assert(listener); tech_pvt->read_frame.data = tech_pvt->databuf; tech_pvt->read_frame.buflen = sizeof(tech_pvt->databuf); switch_mutex_init(&tech_pvt->mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session)); switch_mutex_init(&tech_pvt->flag_mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session)); tech_pvt->call_id = listener->profile->next_call_id++; tech_pvt->listener = listener; tech_pvt->line = line; switch_core_session_set_private(session, tech_pvt); tech_pvt->session = session; skinny_line_get(listener, line, &button); tech_pvt->line_name = switch_core_strdup(listener->pool, button->name); tech_pvt->line_shortname = switch_core_strdup(listener->pool, button->shortname); tech_pvt->line_displayname = switch_core_strdup(listener->pool, button->displayname); } /* State methods they get called when the state changes to the specific state returning SWITCH_STATUS_SUCCESS tells the core to execute the standard state method next so if you fully implement the state you can return SWITCH_STATUS_FALSE to skip it. */ switch_status_t channel_on_init(switch_core_session_t *session) { switch_channel_t *channel; private_t *tech_pvt = NULL; tech_pvt = switch_core_session_get_private(session); assert(tech_pvt != NULL); channel = switch_core_session_get_channel(session); assert(channel != NULL); switch_set_flag_locked(tech_pvt, TFLAG_IO); /* Move channel's state machine to ROUTING. This means the call is trying to get from the initial start where the call because, to the point where a destination has been identified. If the channel is simply left in the initial state, nothing will happen. */ switch_channel_set_state(channel, CS_ROUTING); switch_mutex_lock(globals.calls_mutex); globals.calls++; switch_mutex_unlock(globals.calls_mutex); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s CHANNEL INIT\n", switch_channel_get_name(channel)); return SWITCH_STATUS_SUCCESS; } switch_status_t channel_on_routing(switch_core_session_t *session) { switch_channel_t *channel = NULL; private_t *tech_pvt = NULL; channel = switch_core_session_get_channel(session); assert(channel != NULL); tech_pvt = switch_core_session_get_private(session); assert(tech_pvt != NULL); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s CHANNEL ROUTING\n", switch_channel_get_name(channel)); return SWITCH_STATUS_SUCCESS; } switch_status_t channel_on_execute(switch_core_session_t *session) { switch_channel_t *channel = NULL; private_t *tech_pvt = NULL; channel = switch_core_session_get_channel(session); assert(channel != NULL); tech_pvt = switch_core_session_get_private(session); assert(tech_pvt != NULL); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s CHANNEL EXECUTE\n", switch_channel_get_name(channel)); return SWITCH_STATUS_SUCCESS; } switch_status_t channel_on_destroy(switch_core_session_t *session) { switch_channel_t *channel = NULL; private_t *tech_pvt = NULL; channel = switch_core_session_get_channel(session); assert(channel != NULL); tech_pvt = switch_core_session_get_private(session); if (tech_pvt) { if (switch_core_codec_ready(&tech_pvt->read_codec)) { switch_core_codec_destroy(&tech_pvt->read_codec); } if (switch_core_codec_ready(&tech_pvt->write_codec)) { switch_core_codec_destroy(&tech_pvt->write_codec); } } switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s CHANNEL DESTROY\n", switch_channel_get_name(channel)); return SWITCH_STATUS_SUCCESS; } switch_status_t channel_on_hangup(switch_core_session_t *session) { switch_channel_t *channel = NULL; private_t *tech_pvt = NULL; listener_t *listener = NULL; channel = switch_core_session_get_channel(session); assert(channel != NULL); tech_pvt = switch_core_session_get_private(session); assert(tech_pvt != NULL); listener = tech_pvt->listener; assert(listener != NULL); switch_clear_flag_locked(tech_pvt, TFLAG_IO); switch_clear_flag_locked(tech_pvt, TFLAG_VOICE); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s CHANNEL HANGUP\n", switch_channel_get_name(channel)); listener->session[tech_pvt->line] = NULL; stop_tone(listener, tech_pvt->line, tech_pvt->call_id); set_lamp(listener, SKINNY_BUTTON_LINE, tech_pvt->line, SKINNY_LAMP_OFF); clear_prompt_status(listener, tech_pvt->line, tech_pvt->call_id); if( skinny_line_get_state(tech_pvt->listener, tech_pvt->line) == SKINNY_KEY_SET_CONNECTED ) { close_receive_channel(listener, tech_pvt->call_id, /* uint32_t conference_id, */ tech_pvt->party_id, /* uint32_t pass_thru_party_id, */ tech_pvt->call_id /* uint32_t conference_id2, */ ); stop_media_transmission(listener, tech_pvt->call_id, /* uint32_t conference_id, */ tech_pvt->party_id, /* uint32_t pass_thru_party_id, */ tech_pvt->call_id /* uint32_t conference_id2, */ ); switch_mutex_lock(globals.calls_mutex); globals.calls--; if (globals.calls < 0) { globals.calls = 0; } switch_mutex_unlock(globals.calls_mutex); } send_call_state(listener, SKINNY_ON_HOOK, tech_pvt->line, tech_pvt->call_id); skinny_line_set_state(listener, tech_pvt->line, SKINNY_KEY_SET_ON_HOOK, tech_pvt->call_id); /* TODO: DefineTimeDate */ set_speaker_mode(listener, SKINNY_SPEAKER_OFF); set_ringer(listener, SKINNY_RING_OFF, SKINNY_RING_FOREVER, 0); return SWITCH_STATUS_SUCCESS; } switch_status_t channel_kill_channel(switch_core_session_t *session, int sig) { switch_channel_t *channel = NULL; private_t *tech_pvt = NULL; channel = switch_core_session_get_channel(session); assert(channel != NULL); tech_pvt = switch_core_session_get_private(session); assert(tech_pvt != NULL); switch (sig) { case SWITCH_SIG_KILL: switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); break; case SWITCH_SIG_BREAK: if (switch_rtp_ready(tech_pvt->rtp_session)) { switch_rtp_break(tech_pvt->rtp_session); } break; default: break; } switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s CHANNEL KILL %d\n", switch_channel_get_name(channel), sig); return SWITCH_STATUS_SUCCESS; } switch_status_t channel_on_exchange_media(switch_core_session_t *session) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "CHANNEL LOOPBACK\n"); return SWITCH_STATUS_SUCCESS; } switch_status_t channel_on_soft_execute(switch_core_session_t *session) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "CHANNEL TRANSMIT\n"); return SWITCH_STATUS_SUCCESS; } switch_status_t channel_send_dtmf(switch_core_session_t *session, const switch_dtmf_t *dtmf) { private_t *tech_pvt = switch_core_session_get_private(session); switch_assert(tech_pvt != NULL); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "DTMF ON CALL %d [%c]\n", tech_pvt->call_id, dtmf->digit); return SWITCH_STATUS_SUCCESS; } switch_status_t channel_read_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, int stream_id) { switch_channel_t *channel = NULL; private_t *tech_pvt = NULL; int payload = 0; channel = switch_core_session_get_channel(session); assert(channel != NULL); tech_pvt = switch_core_session_get_private(session); assert(tech_pvt != NULL); while (!(tech_pvt->read_codec.implementation && switch_rtp_ready(tech_pvt->rtp_session))) { if (switch_channel_ready(channel)) { switch_yield(10000); } else { return SWITCH_STATUS_GENERR; } } tech_pvt->read_frame.datalen = 0; switch_set_flag_locked(tech_pvt, TFLAG_READING); if (switch_test_flag(tech_pvt, TFLAG_IO)) { switch_status_t status; switch_assert(tech_pvt->rtp_session != NULL); tech_pvt->read_frame.datalen = 0; while (switch_test_flag(tech_pvt, TFLAG_IO) && tech_pvt->read_frame.datalen == 0) { tech_pvt->read_frame.flags = SFF_NONE; status = switch_rtp_zerocopy_read_frame(tech_pvt->rtp_session, &tech_pvt->read_frame, flags); if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) { return SWITCH_STATUS_FALSE; } payload = tech_pvt->read_frame.payload; if (switch_rtp_has_dtmf(tech_pvt->rtp_session)) { switch_dtmf_t dtmf = { 0 }; switch_rtp_dequeue_dtmf(tech_pvt->rtp_session, &dtmf); switch_channel_queue_dtmf(channel, &dtmf); } if (tech_pvt->read_frame.datalen > 0) { size_t bytes = 0; int frames = 1; if (!switch_test_flag((&tech_pvt->read_frame), SFF_CNG)) { if ((bytes = tech_pvt->read_codec.implementation->encoded_bytes_per_packet)) { frames = (tech_pvt->read_frame.datalen / bytes); } tech_pvt->read_frame.samples = (int) (frames * tech_pvt->read_codec.implementation->samples_per_packet); } break; } } } switch_clear_flag_locked(tech_pvt, TFLAG_READING); if (tech_pvt->read_frame.datalen == 0) { *frame = NULL; return SWITCH_STATUS_GENERR; } *frame = &tech_pvt->read_frame; return SWITCH_STATUS_SUCCESS; } switch_status_t channel_write_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id) { switch_channel_t *channel = NULL; private_t *tech_pvt = NULL; //switch_frame_t *pframe; switch_status_t status = SWITCH_STATUS_SUCCESS; channel = switch_core_session_get_channel(session); assert(channel != NULL); tech_pvt = switch_core_session_get_private(session); assert(tech_pvt != NULL); if (!switch_test_flag(tech_pvt, TFLAG_IO)) { return SWITCH_STATUS_FALSE; } #if SWITCH_BYTE_ORDER == __BIG_ENDIAN if (switch_test_flag(tech_pvt, TFLAG_LINEAR)) { switch_swap_linear(frame->data, (int) frame->datalen / 2); } #endif switch_set_flag_locked(tech_pvt, TFLAG_WRITING); switch_rtp_write_frame(tech_pvt->rtp_session, frame); switch_clear_flag_locked(tech_pvt, TFLAG_WRITING); return status; } switch_status_t channel_answer_channel(switch_core_session_t *session) { private_t *tech_pvt; switch_channel_t *channel = NULL; channel = switch_core_session_get_channel(session); assert(channel != NULL); tech_pvt = switch_core_session_get_private(session); assert(tech_pvt != NULL); return SWITCH_STATUS_SUCCESS; } switch_status_t channel_receive_message(switch_core_session_t *session, switch_core_session_message_t *msg) { switch_channel_t *channel; private_t *tech_pvt; channel = switch_core_session_get_channel(session); assert(channel != NULL); tech_pvt = (private_t *) switch_core_session_get_private(session); assert(tech_pvt != NULL); switch (msg->message_id) { case SWITCH_MESSAGE_INDICATE_ANSWER: { channel_answer_channel(session); } break; default: break; } return SWITCH_STATUS_SUCCESS; } /* Make sure when you have 2 sessions in the same scope that you pass the appropriate one to the routines that allocate memory or you will have 1 channel with memory allocated from another channel's pool! */ switch_call_cause_t channel_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_DESTINATION_OUT_OF_ORDER; switch_core_session_t *nsession = NULL; private_t *tech_pvt; char *profile_name, *dest; skinny_profile_t *profile = NULL; listener_t *listener = NULL; uint32_t line = 0; char name[128]; switch_channel_t *channel; switch_caller_profile_t *caller_profile; if (!outbound_profile || zstr(outbound_profile->destination_number)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Invalid Destination\n"); goto error; } if (!(nsession = switch_core_session_request(skinny_endpoint_interface, SWITCH_CALL_DIRECTION_OUTBOUND, pool))) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Error Creating Session\n"); goto error; } if (!(tech_pvt = (struct private_object *) switch_core_session_alloc(nsession, sizeof(*tech_pvt)))) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT, "Error Creating Session private object\n"); goto error; } if(!(profile_name = switch_core_session_strdup(nsession, outbound_profile->destination_number))) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_CRIT, "Error Creating Session Info\n"); goto error; } if (!(dest = strchr(profile_name, '/'))) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Invalid Skinny URL. Should be skinny//.\n"); cause = SWITCH_CAUSE_INVALID_NUMBER_FORMAT; goto error; } *dest++ = '\0'; profile = skinny_find_profile(profile_name); if (!(profile = skinny_find_profile(profile_name))) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Invalid Profile %s\n", profile_name); cause = SWITCH_CAUSE_UNALLOCATED_NUMBER; goto error; } snprintf(name, sizeof(name), "SKINNY/%s/%s", profile->name, dest); channel = switch_core_session_get_channel(nsession); switch_channel_set_name(channel, name); if ((skinny_profile_find_listener_by_dest(profile, dest, &listener, &line) != SWITCH_STATUS_SUCCESS)) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Problem while retrieving listener and line for destination %s in profile %s\n", dest, profile_name); cause = SWITCH_CAUSE_UNALLOCATED_NUMBER; goto error; } if (!listener) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Invalid destination or phone not registred %s in profile %s\n", dest, profile_name); cause = SWITCH_CAUSE_UNALLOCATED_NUMBER; goto error; } if (line == 0) { switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Invalid destination or phone not registred %s in profile %s\n", dest, profile_name); cause = SWITCH_CAUSE_UNALLOCATED_NUMBER; goto error; } tech_init(tech_pvt, nsession, listener, line); caller_profile = switch_caller_profile_clone(nsession, outbound_profile); switch_channel_set_caller_profile(channel, caller_profile); tech_pvt->caller_profile = caller_profile; switch_channel_set_flag(channel, CF_OUTBOUND); switch_set_flag_locked(tech_pvt, TFLAG_OUTBOUND); if(tech_pvt->listener->session[tech_pvt->line]) { /* Line is busy */ switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Device line is busy %s in profile %s\n", dest, profile_name); cause = SWITCH_CAUSE_USER_BUSY; goto error; } tech_pvt->listener->session[tech_pvt->line] = nsession; send_call_state(tech_pvt->listener, SKINNY_RING_IN, tech_pvt->line, tech_pvt->call_id); skinny_line_set_state(tech_pvt->listener, tech_pvt->line, SKINNY_KEY_SET_RING_IN, tech_pvt->call_id); display_prompt_status(tech_pvt->listener, 0, "\200\027tel", tech_pvt->line, tech_pvt->call_id); /* displayprinotifiymessage */ skinny_send_call_info(nsession); set_lamp(tech_pvt->listener, SKINNY_BUTTON_LINE, tech_pvt->line, SKINNY_LAMP_BLINK); set_ringer(tech_pvt->listener, SKINNY_RING_OUTSIDE, SKINNY_RING_FOREVER, 0); *new_session = nsession; /* ?? switch_channel_mark_ring_ready(channel); */ if (switch_channel_get_state(channel) == CS_NEW) { switch_channel_set_state(channel, CS_INIT); } cause = SWITCH_CAUSE_SUCCESS; goto done; error: if (nsession) { switch_core_session_destroy(&nsession); } *pool = NULL; done: if (profile) { if (cause == SWITCH_CAUSE_SUCCESS) { profile->ob_calls++; } else { profile->ob_failed_calls++; } } return cause; } switch_status_t channel_receive_event(switch_core_session_t *session, switch_event_t *event) { struct private_object *tech_pvt = switch_core_session_get_private(session); char *body = switch_event_get_body(event); switch_assert(tech_pvt != NULL); if (!body) { body = ""; } return SWITCH_STATUS_SUCCESS; } switch_state_handler_table_t skinny_state_handlers = { /*.on_init */ channel_on_init, /*.on_routing */ channel_on_routing, /*.on_execute */ channel_on_execute, /*.on_hangup */ channel_on_hangup, /*.on_exchange_media */ channel_on_exchange_media, /*.on_soft_execute */ channel_on_soft_execute, /*.on_consume_media*/ NULL, /*.on_hibernate*/ NULL, /*.on_reset*/ NULL, /*.on_park*/ NULL, /*.on_reporting*/ NULL, /*.on_destroy*/ channel_on_destroy }; switch_io_routines_t skinny_io_routines = { /*.outgoing_channel */ channel_outgoing_channel, /*.read_frame */ channel_read_frame, /*.write_frame */ channel_write_frame, /*.kill_channel */ channel_kill_channel, /*.send_dtmf */ channel_send_dtmf, /*.receive_message */ channel_receive_message, /*.receive_event */ channel_receive_event }; /*****************************************************************************/ /* LISTENER FUNCTIONS */ /*****************************************************************************/ static void add_listener(listener_t *listener) { skinny_profile_t *profile; switch_assert(listener); assert(listener->profile); profile = listener->profile; switch_mutex_lock(profile->listener_mutex); listener->next = profile->listeners; profile->listeners = listener; switch_mutex_unlock(profile->listener_mutex); } static void remove_listener(listener_t *listener) { listener_t *l, *last = NULL; skinny_profile_t *profile; switch_assert(listener); assert(listener->profile); profile = listener->profile; switch_mutex_lock(profile->listener_mutex); for (l = profile->listeners; l; l = l->next) { if (l == listener) { if (last) { last->next = l->next; } else { profile->listeners = l->next; } } last = l; } switch_mutex_unlock(profile->listener_mutex); } static void walk_listeners(skinny_listener_callback_func_t callback, void *pvt) { switch_hash_index_t *hi; void *val; skinny_profile_t *profile; listener_t *l; /* walk listeners */ for (hi = switch_hash_first(NULL, globals.profile_hash); hi; hi = switch_hash_next(hi)) { switch_hash_this(hi, NULL, NULL, &val); profile = (skinny_profile_t *) val; switch_mutex_lock(profile->listener_mutex); for (l = profile->listeners; l; l = l->next) { callback(l, pvt); } switch_mutex_unlock(profile->listener_mutex); } } static void flush_listener(listener_t *listener, switch_bool_t flush_log, switch_bool_t flush_events) { /* TODO */ } static int dump_device_callback(void *pArg, int argc, char **argv, char **columnNames) { switch_stream_handle_t *stream = (switch_stream_handle_t *) pArg; char *device_name = argv[0]; char *user_id = argv[1]; char *instance = argv[2]; char *ip = argv[3]; char *type = argv[4]; char *max_streams = argv[5]; char *port = argv[6]; char *codec_string = argv[7]; const char *line = "================================================================================================="; stream->write_function(stream, "%s\n", line); stream->write_function(stream, "DeviceName \t%s\n", switch_str_nil(device_name)); stream->write_function(stream, "UserId \t%s\n", user_id); stream->write_function(stream, "Instance \t%s\n", instance); stream->write_function(stream, "IP \t%s\n", ip); stream->write_function(stream, "DeviceType \t%s\n", type); stream->write_function(stream, "MaxStreams \t%s\n", max_streams); stream->write_function(stream, "Port \t%s\n", port); stream->write_function(stream, "Codecs \t%s\n", codec_string); stream->write_function(stream, "%s\n", line); return 0; } static switch_status_t dump_device(skinny_profile_t *profile, const char *device_name, switch_stream_handle_t *stream) { char *sql; if ((sql = switch_mprintf("SELECT * FROM skinny_devices WHERE name LIKE '%s'", device_name))) { skinny_execute_sql_callback(profile, profile->listener_mutex, sql, dump_device_callback, stream); switch_safe_free(sql); } return SWITCH_STATUS_SUCCESS; } static void close_socket(switch_socket_t **sock, skinny_profile_t *profile) { switch_mutex_lock(profile->sock_mutex); if (*sock) { switch_socket_shutdown(*sock, SWITCH_SHUTDOWN_READWRITE); switch_socket_close(*sock); *sock = NULL; } switch_mutex_unlock(profile->sock_mutex); } static switch_status_t kill_listener(listener_t *listener, void *pvt) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Killing listener.\n"); switch_clear_flag(listener, LFLAG_RUNNING); close_socket(&listener->sock, listener->profile); return SWITCH_STATUS_SUCCESS; } static switch_status_t kill_expired_listener(listener_t *listener, void *pvt) { switch_event_t *event = NULL; if(listener->expire_time < switch_epoch_time_now(NULL)) { /* skinny::expire event */ skinny_device_event(listener, &event, SWITCH_EVENT_CUSTOM, SKINNY_EVENT_EXPIRE); switch_event_fire(&event); return kill_listener(listener, pvt); } return SWITCH_STATUS_SUCCESS; } switch_status_t keepalive_listener(listener_t *listener, void *pvt) { skinny_profile_t *profile; switch_assert(listener); assert(listener->profile); profile = listener->profile; listener->expire_time = switch_epoch_time_now(NULL)+profile->keep_alive*110/100; return SWITCH_STATUS_SUCCESS; } static void *SWITCH_THREAD_FUNC listener_run(switch_thread_t *thread, void *obj) { listener_t *listener = (listener_t *) obj; switch_status_t status; skinny_message_t *request = NULL; skinny_profile_t *profile; int destroy_pool = 1; switch_assert(listener); assert(listener->profile); profile = listener->profile; switch_mutex_lock(profile->listener_mutex); profile->listener_threads++; switch_mutex_unlock(profile->listener_mutex); switch_assert(listener != NULL); switch_socket_opt_set(listener->sock, SWITCH_SO_TCP_NODELAY, TRUE); switch_socket_opt_set(listener->sock, SWITCH_SO_NONBLOCK, TRUE); if (globals.debug > 0) { if (zstr(listener->remote_ip)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Connection Open\n"); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Connection Open from %s:%d\n", listener->remote_ip, listener->remote_port); } } switch_socket_opt_set(listener->sock, SWITCH_SO_NONBLOCK, TRUE); switch_set_flag_locked(listener, LFLAG_RUNNING); keepalive_listener(listener, NULL); add_listener(listener); while (globals.running && switch_test_flag(listener, LFLAG_RUNNING) && profile->listener_ready) { status = skinny_read_packet(listener, &request); if (status != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Socket Error!\n"); switch_clear_flag_locked(listener, LFLAG_RUNNING); break; } if (!request) { continue; } if (skinny_handle_request(listener, request) != SWITCH_STATUS_SUCCESS) { switch_clear_flag_locked(listener, LFLAG_RUNNING); break; } } remove_listener(listener); if (globals.debug > 0) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Session complete, waiting for children\n"); } switch_thread_rwlock_wrlock(listener->rwlock); flush_listener(listener, SWITCH_TRUE, SWITCH_TRUE); if (listener->sock) { close_socket(&listener->sock, profile); } switch_thread_rwlock_unlock(listener->rwlock); if (globals.debug > 0) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Connection Closed\n"); } for(int line = 0 ; line < SKINNY_MAX_BUTTON_COUNT ; line++) { if(listener->session[line]) { switch_channel_clear_flag(switch_core_session_get_channel(listener->session[line]), CF_CONTROLLED); //TODO switch_clear_flag_locked(listener, LFLAG_SESSION); switch_core_session_rwunlock(listener->session[line]); destroy_pool = 0; } } if(destroy_pool == 0) { goto no_destroy_pool; } if (listener->pool) { switch_memory_pool_t *pool = listener->pool; switch_core_destroy_memory_pool(&pool); } no_destroy_pool: switch_mutex_lock(profile->listener_mutex); profile->listener_threads--; switch_mutex_unlock(profile->listener_mutex); return NULL; } /* Create a thread for the socket and launch it */ static void launch_listener_thread(listener_t *listener) { switch_thread_t *thread; switch_threadattr_t *thd_attr = NULL; switch_threadattr_create(&thd_attr, listener->pool); switch_threadattr_detach_set(thd_attr, 1); switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE); switch_thread_create(&thread, thd_attr, listener_run, listener, listener->pool); } int skinny_socket_create_and_bind(skinny_profile_t *profile) { switch_status_t rv; switch_sockaddr_t *sa; switch_socket_t *inbound_socket = NULL; listener_t *listener; switch_memory_pool_t *pool = NULL, *listener_pool = NULL; uint32_t errs = 0; if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "OH OH no pool\n"); return SWITCH_STATUS_TERM; } while(globals.running) { rv = switch_sockaddr_info_get(&sa, profile->ip, SWITCH_INET, profile->port, 0, pool); if (rv) goto fail; rv = switch_socket_create(&profile->sock, switch_sockaddr_get_family(sa), SOCK_STREAM, SWITCH_PROTO_TCP, pool); if (rv) goto sock_fail; rv = switch_socket_opt_set(profile->sock, SWITCH_SO_REUSEADDR, 1); if (rv) goto sock_fail; rv = switch_socket_bind(profile->sock, sa); if (rv) goto sock_fail; rv = switch_socket_listen(profile->sock, 5); if (rv) goto sock_fail; switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Socket up listening on %s:%u\n", profile->ip, profile->port); break; sock_fail: switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Socket Error! Could not listen on %s:%u\n", profile->ip, profile->port); switch_yield(100000); } profile->listener_ready = 1; while(globals.running) { if (switch_core_new_memory_pool(&listener_pool) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "OH OH no pool\n"); goto fail; } if ((rv = switch_socket_accept(&inbound_socket, profile->sock, listener_pool))) { if (!globals.running) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Shutting Down\n"); goto end; } else { /* I wish we could use strerror_r here but its not defined everywhere =/ */ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Socket Error [%s]\n", strerror(errno)); if (++errs > 100) { goto end; } } } else { errs = 0; } if (!(listener = switch_core_alloc(listener_pool, sizeof(*listener)))) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Memory Error\n"); break; } switch_thread_rwlock_create(&listener->rwlock, listener_pool); listener->sock = inbound_socket; listener->pool = listener_pool; listener_pool = NULL; strcpy(listener->device_name, ""); listener->profile = profile; switch_mutex_init(&listener->flag_mutex, SWITCH_MUTEX_NESTED, listener->pool); switch_socket_addr_get(&listener->sa, SWITCH_TRUE, listener->sock); switch_get_addr(listener->remote_ip, sizeof(listener->remote_ip), listener->sa); listener->remote_port = switch_sockaddr_get_port(listener->sa); launch_listener_thread(listener); } end: close_socket(&profile->sock, profile); if (pool) { switch_core_destroy_memory_pool(&pool); } if (listener_pool) { switch_core_destroy_memory_pool(&listener_pool); } fail: return SWITCH_STATUS_TERM; } /*****************************************************************************/ /* MODULE FUNCTIONS */ /*****************************************************************************/ switch_endpoint_interface_t *skinny_get_endpoint_interface() { return skinny_endpoint_interface; } static void skinny_profile_set(skinny_profile_t *profile, char *var, char *val) { if (!var) return; if (!strcasecmp(var, "domain")) { profile->domain = switch_core_strdup(module_pool, val); } else if (!strcasecmp(var, "ip")) { profile->ip = switch_core_strdup(module_pool, val); } else if (!strcasecmp(var, "dialplan")) { profile->dialplan = switch_core_strdup(module_pool, val); } else if (!strcasecmp(var, "context")) { profile->context = switch_core_strdup(module_pool, val); } else if (!strcasecmp(var, "odbc-dsn") && !zstr(val)) { if (switch_odbc_available()) { profile->odbc_dsn = switch_core_strdup(module_pool, val); if ((profile->odbc_user = strchr(profile->odbc_dsn, ':'))) { *profile->odbc_user++ = '\0'; if ((profile->odbc_pass = strchr(profile->odbc_user, ':'))) { *profile->odbc_pass++ = '\0'; } } } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "ODBC IS NOT AVAILABLE!\n"); } } } static switch_status_t load_skinny_config(void) { char *cf = "skinny.conf"; switch_xml_t xcfg, xml, xsettings, xprofiles, xprofile, xparam; memset(&globals, 0, sizeof(globals)); globals.running = 1; switch_core_hash_init(&globals.profile_hash, module_pool); switch_mutex_init(&globals.calls_mutex, SWITCH_MUTEX_NESTED, module_pool); if (!(xml = switch_xml_open_cfg(cf, &xcfg, NULL))) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Open of %s failed\n", cf); return SWITCH_STATUS_TERM; } if ((xsettings = switch_xml_child(xcfg, "settings"))) { for (xparam = switch_xml_child(xsettings, "param"); xparam; xparam = xparam->next) { char *var = (char *) switch_xml_attr_soft(xparam, "name"); char *val = (char *) switch_xml_attr_soft(xparam, "value"); if (!strcmp(var, "debug")) { globals.debug = atoi(val); } else if (!strcmp(var, "codec-prefs")) { set_global_codec_string(val); globals.codec_order_last = switch_separate_string(globals.codec_string, ',', globals.codec_order, SWITCH_MAX_CODECS); } else if (!strcmp(var, "codec-master")) { if (!strcasecmp(val, "us")) { switch_set_flag(&globals, GFLAG_MY_CODEC_PREFS); } } else if (!strcmp(var, "codec-rates")) { set_global_codec_rates_string(val); globals.codec_rates_last = switch_separate_string(globals.codec_rates_string, ',', globals.codec_rates, SWITCH_MAX_CODECS); } } /* param */ } /* settings */ if ((xprofiles = switch_xml_child(xcfg, "profiles"))) { for (xprofile = switch_xml_child(xprofiles, "profile"); xprofile; xprofile = xprofile->next) { char *profile_name = (char *) switch_xml_attr_soft(xprofile, "name"); switch_xml_t xsettings = switch_xml_child(xprofile, "settings"); if (zstr(profile_name)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, " is missing name attribute\n"); continue; } if (xsettings) { char dbname[256]; switch_core_db_t *db; skinny_profile_t *profile = NULL; switch_xml_t param; profile = switch_core_alloc(module_pool, sizeof(skinny_profile_t)); profile->name = profile_name; for (param = switch_xml_child(xsettings, "param"); param; param = param->next) { char *var = (char *) switch_xml_attr_soft(param, "name"); char *val = (char *) switch_xml_attr_soft(param, "value"); if (!strcmp(var, "domain")) { skinny_profile_set(profile, "domain", val); } else if (!strcmp(var, "ip")) { skinny_profile_set(profile, "ip", val); } else if (!strcmp(var, "port")) { profile->port = atoi(val); } else if (!strcmp(var, "dialplan")) { skinny_profile_set(profile, "dialplan", val); } else if (!strcmp(var, "context")) { skinny_profile_set(profile, "context", val); } else if (!strcmp(var, "keep-alive")) { profile->keep_alive = atoi(val); } else if (!strcmp(var, "date-format")) { memcpy(profile->date_format, val, 6); } } /* param */ if (!profile->dialplan) { skinny_profile_set(profile, "dialplan","default"); } if (!profile->context) { skinny_profile_set(profile, "context","public"); } if (!profile->port) { profile->port = 2000; } switch_snprintf(dbname, sizeof(dbname), "skinny_%s", profile->name); profile->dbname = switch_core_strdup(module_pool, dbname); if (switch_odbc_available() && profile->odbc_dsn) { if (!(profile->master_odbc = switch_odbc_handle_new(profile->odbc_dsn, profile->odbc_user, profile->odbc_pass))) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Cannot Open ODBC Database!\n"); continue; } if (switch_odbc_handle_connect(profile->master_odbc) != SWITCH_ODBC_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Cannot Open ODBC Database!\n"); continue; } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Connected ODBC DSN: %s\n", profile->odbc_dsn); switch_odbc_handle_exec(profile->master_odbc, devices_sql, NULL, NULL); switch_odbc_handle_exec(profile->master_odbc, buttons_sql, NULL, NULL); } else { if ((db = switch_core_db_open_file(profile->dbname))) { switch_core_db_test_reactive(db, "SELECT * FROM skinny_devices", NULL, devices_sql); switch_core_db_test_reactive(db, "SELECT * FROM skinny_buttons", NULL, buttons_sql); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Cannot Open SQL Database!\n"); continue; } switch_core_db_close(db); } skinny_execute_sql_callback(profile, profile->listener_mutex, "DELETE FROM skinny_devices", NULL, NULL); skinny_execute_sql_callback(profile, profile->listener_mutex, "DELETE FROM skinny_buttons", NULL, NULL); switch_core_hash_insert(globals.profile_hash, profile->name, profile); profile = NULL; } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Settings are missing from profile %s.\n", profile_name); } /* settings */ } /* profile */ } switch_xml_free(xml); return SWITCH_STATUS_SUCCESS; } static switch_status_t cmd_status_profile(const char *profile_name, switch_stream_handle_t *stream) { skinny_profile_t *profile; if ((profile = skinny_find_profile(profile_name))) { dump_profile(profile, stream); } else { stream->write_function(stream, "Profile not found!\n"); } return SWITCH_STATUS_SUCCESS; } static switch_status_t cmd_status_profile_device(const char *profile_name, const char *device_name, switch_stream_handle_t *stream) { skinny_profile_t *profile; if ((profile = skinny_find_profile(profile_name))) { dump_device(profile, device_name, stream); } else { stream->write_function(stream, "Profile not found!\n"); } return SWITCH_STATUS_SUCCESS; } static switch_status_t cmd_profile_device_send_ringer_message(const char *profile_name, const char *device_name, const char *ring_type, const char *ring_mode, switch_stream_handle_t *stream) { skinny_profile_t *profile; if ((profile = skinny_find_profile(profile_name))) { listener_t *listener = NULL; skinny_profile_find_listener_by_device_name(profile, device_name, &listener); if(listener) { set_ringer(listener, skinny_str2ring_type(ring_type), skinny_str2ring_mode(ring_mode), 0); } else { stream->write_function(stream, "Listener not found!\n"); } } else { stream->write_function(stream, "Profile not found!\n"); } return SWITCH_STATUS_SUCCESS; } static switch_status_t cmd_profile_device_send_lamp_message(const char *profile_name, const char *device_name, const char *stimulus, const char *instance, const char *lamp_mode, switch_stream_handle_t *stream) { skinny_profile_t *profile; if ((profile = skinny_find_profile(profile_name))) { listener_t *listener = NULL; skinny_profile_find_listener_by_device_name(profile, device_name, &listener); if(listener) { set_lamp(listener, skinny_str2stimulus(stimulus), atoi(instance), skinny_str2lamp_mode(lamp_mode)); } else { stream->write_function(stream, "Listener not found!\n"); } } else { stream->write_function(stream, "Profile not found!\n"); } return SWITCH_STATUS_SUCCESS; } static switch_status_t cmd_profile_device_send_speaker_mode_message(const char *profile_name, const char *device_name, const char *speaker_mode, switch_stream_handle_t *stream) { skinny_profile_t *profile; if ((profile = skinny_find_profile(profile_name))) { listener_t *listener = NULL; skinny_profile_find_listener_by_device_name(profile, device_name, &listener); if(listener) { set_speaker_mode(listener, skinny_str2speaker_mode(speaker_mode)); } else { stream->write_function(stream, "Listener not found!\n"); } } else { stream->write_function(stream, "Profile not found!\n"); } return SWITCH_STATUS_SUCCESS; } static switch_status_t cmd_profile_device_send_call_state_message(const char *profile_name, const char *device_name, const char *call_state, const char *line_instance, const char *call_id, switch_stream_handle_t *stream) { skinny_profile_t *profile; if ((profile = skinny_find_profile(profile_name))) { listener_t *listener = NULL; skinny_profile_find_listener_by_device_name(profile, device_name, &listener); if(listener) { send_call_state(listener, skinny_str2call_state(call_state), atoi(line_instance), atoi(call_id)); } else { stream->write_function(stream, "Listener not found!\n"); } } else { stream->write_function(stream, "Profile not found!\n"); } return SWITCH_STATUS_SUCCESS; } SWITCH_STANDARD_API(skinny_function) { char *argv[1024] = { 0 }; int argc = 0; char *mycmd = NULL; switch_status_t status = SWITCH_STATUS_SUCCESS; const char *usage_string = "USAGE:\n" "--------------------------------------------------------------------------------\n" "skinny help\n" "skinny status profile \n" "skinny status profile device \n" "skinny profile device send SetRingerMessage \n" "skinny profile device send SetLampMessage \n" "skinny profile device send SetSpeakerModeMessage \n" "skinny profile device send CallState \n" "--------------------------------------------------------------------------------\n"; if (session) { return SWITCH_STATUS_FALSE; } if (zstr(cmd)) { stream->write_function(stream, "%s", usage_string); goto done; } if (!(mycmd = strdup(cmd))) { status = SWITCH_STATUS_MEMERR; goto done; } if (!(argc = switch_separate_string(mycmd, ' ', argv, (sizeof(argv) / sizeof(argv[0])))) || !argv[0]) { stream->write_function(stream, "%s", usage_string); goto done; } if (!strcasecmp(argv[0], "help")) {/* skinny help */ stream->write_function(stream, "%s", usage_string); goto done; } else if (argc == 3 && !strcasecmp(argv[0], "status") && !strcasecmp(argv[1], "profile")) { /* skinny status profile */ status = cmd_status_profile(argv[2], stream); } else if (argc == 5 && !strcasecmp(argv[0], "status") && !strcasecmp(argv[1], "profile") && !strcasecmp(argv[3], "device")) { /* skinny status profile device */ status = cmd_status_profile_device(argv[2], argv[4], stream); } else if (argc == 8 && !strcasecmp(argv[0], "profile") && !strcasecmp(argv[2], "device") && !strcasecmp(argv[4], "send") && !strcasecmp(argv[5], "SetRingerMessage")) { /* skinny profile device send SetRingerMessage */ status = cmd_profile_device_send_ringer_message(argv[1], argv[3], argv[6], argv[7], stream); } else if (argc == 9 && !strcasecmp(argv[0], "profile") && !strcasecmp(argv[2], "device") && !strcasecmp(argv[4], "send") && !strcasecmp(argv[5], "SetLampMessage")) { /* skinny profile device send SetLampMessage */ status = cmd_profile_device_send_lamp_message(argv[1], argv[3], argv[6], argv[7], argv[8], stream); } else if (argc == 7 && !strcasecmp(argv[0], "profile") && !strcasecmp(argv[2], "device") && !strcasecmp(argv[4], "send") && !strcasecmp(argv[5], "SetSpeakerModeMessage")) { /* skinny profile device send SetSpeakerModeMessage */ status = cmd_profile_device_send_speaker_mode_message(argv[1], argv[3], argv[6], stream); } else if (argc == 9 && !strcasecmp(argv[0], "profile") && !strcasecmp(argv[2], "device") && !strcasecmp(argv[4], "send") && !strcasecmp(argv[5], "CallState")) { /* skinny profile device send CallState */ status = cmd_profile_device_send_call_state_message(argv[1], argv[3], argv[6], argv[7], argv[8], stream); } else { stream->write_function(stream, "Unknown Command [%s]\n", argv[0]); } done: switch_safe_free(mycmd); return status; } static void event_handler(switch_event_t *event) { if (event->event_id == SWITCH_EVENT_HEARTBEAT) { walk_listeners(kill_expired_listener, NULL); } } static switch_status_t skinny_list_profiles(const char *line, const char *cursor, switch_console_callback_match_t **matches) { switch_console_callback_match_t *my_matches = NULL; switch_status_t status = SWITCH_STATUS_FALSE; switch_hash_index_t *hi; void *val; skinny_profile_t *profile; /* walk profiles */ for (hi = switch_hash_first(NULL, globals.profile_hash); hi; hi = switch_hash_next(hi)) { switch_hash_this(hi, NULL, NULL, &val); profile = (skinny_profile_t *) val; switch_console_push_match(&my_matches, profile->name); } if (my_matches) { *matches = my_matches; status = SWITCH_STATUS_SUCCESS; } return status; } struct match_helper { switch_console_callback_match_t *my_matches; }; static int skinny_list_devices_callback(void *pArg, int argc, char **argv, char **columnNames) { struct match_helper *h = (struct match_helper *) pArg; char *device_name = argv[0]; switch_console_push_match(&h->my_matches, device_name); return 0; } static switch_status_t skinny_list_devices(const char *line, const char *cursor, switch_console_callback_match_t **matches) { struct match_helper h = { 0 }; switch_status_t status = SWITCH_STATUS_FALSE; skinny_profile_t *profile = NULL; char *sql; char *myline; char *argv[1024] = { 0 }; int argc = 0; if (!(myline = strdup(line))) { status = SWITCH_STATUS_MEMERR; return status; } if (!(argc = switch_separate_string(myline, ' ', argv, (sizeof(argv) / sizeof(argv[0])))) || argc != 5) { return status; } if(!strcasecmp(argv[1], "profile")) {/* skinny profile ... */ profile = skinny_find_profile(argv[2]); } else if(!strcasecmp(argv[2], "profile")) {/* skinny status profile ... */ profile = skinny_find_profile(argv[3]); } if(profile) { if ((sql = switch_mprintf("SELECT name FROM skinny_devices"))) { skinny_execute_sql_callback(profile, profile->listener_mutex, sql, skinny_list_devices_callback, &h); switch_safe_free(sql); } } if (h.my_matches) { *matches = h.my_matches; status = SWITCH_STATUS_SUCCESS; } return status; } static switch_status_t skinny_list_stimuli(const char *line, const char *cursor, switch_console_callback_match_t **matches) { switch_status_t status = SWITCH_STATUS_FALSE; SKINNY_PUSH_STIMULI return status; } static switch_status_t skinny_list_ring_type(const char *line, const char *cursor, switch_console_callback_match_t **matches) { switch_status_t status = SWITCH_STATUS_FALSE; SKINNY_PUSH_RING_TYPES return status; } static switch_status_t skinny_list_ring_mode(const char *line, const char *cursor, switch_console_callback_match_t **matches) { switch_status_t status = SWITCH_STATUS_FALSE; SKINNY_PUSH_RING_MODES return status; } static switch_status_t skinny_list_stimulus_instances(const char *line, const char *cursor, switch_console_callback_match_t **matches) { switch_status_t status = SWITCH_STATUS_FALSE; switch_console_callback_match_t *my_matches = NULL; switch_console_push_match(&my_matches, ""); switch_console_push_match(&my_matches, "0"); if (my_matches) { *matches = my_matches; status = SWITCH_STATUS_SUCCESS; } return status; } static switch_status_t skinny_list_stimulus_modes(const char *line, const char *cursor, switch_console_callback_match_t **matches) { switch_status_t status = SWITCH_STATUS_FALSE; SKINNY_PUSH_LAMP_MODES return status; } static switch_status_t skinny_list_speaker_mode(const char *line, const char *cursor, switch_console_callback_match_t **matches) { switch_status_t status = SWITCH_STATUS_FALSE; SKINNY_PUSH_SPEAKER_MODES return status; } static switch_status_t skinny_list_call_states(const char *line, const char *cursor, switch_console_callback_match_t **matches) { switch_status_t status = SWITCH_STATUS_FALSE; SKINNY_PUSH_CALL_STATES return status; } static switch_status_t skinny_list_line_instances(const char *line, const char *cursor, switch_console_callback_match_t **matches) { switch_status_t status = SWITCH_STATUS_FALSE; switch_console_callback_match_t *my_matches = NULL; /* TODO */ switch_console_push_match(&my_matches, "1"); switch_console_push_match(&my_matches, ""); if (my_matches) { *matches = my_matches; status = SWITCH_STATUS_SUCCESS; } return status; } static switch_status_t skinny_list_call_ids(const char *line, const char *cursor, switch_console_callback_match_t **matches) { switch_status_t status = SWITCH_STATUS_FALSE; switch_console_callback_match_t *my_matches = NULL; /* TODO */ switch_console_push_match(&my_matches, "1345"); switch_console_push_match(&my_matches, ""); if (my_matches) { *matches = my_matches; status = SWITCH_STATUS_SUCCESS; } return status; } /*****************************************************************************/ SWITCH_MODULE_LOAD_FUNCTION(mod_skinny_load) { switch_hash_index_t *hi; void *val; skinny_profile_t *profile; switch_api_interface_t *api_interface; module_pool = pool; load_skinny_config(); /* init listeners */ for (hi = switch_hash_first(NULL, globals.profile_hash); hi; hi = switch_hash_next(hi)) { switch_hash_this(hi, NULL, NULL, &val); profile = (skinny_profile_t *) val; switch_mutex_init(&profile->listener_mutex, SWITCH_MUTEX_NESTED, module_pool); switch_mutex_init(&profile->sock_mutex, SWITCH_MUTEX_NESTED, module_pool); } if ((switch_event_bind_removable(modname, SWITCH_EVENT_HEARTBEAT, NULL, event_handler, NULL, &globals.heartbeat_node) != SWITCH_STATUS_SUCCESS)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't bind our heartbeat handler!\n"); /* Not such severe to prevent loading */ } if (switch_event_reserve_subclass(SKINNY_EVENT_REGISTER) != SWITCH_STATUS_SUCCESS) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't register subclass %s!\n", SKINNY_EVENT_REGISTER); return SWITCH_STATUS_TERM; } /* connect my internal structure to the blank pointer passed to me */ *module_interface = switch_loadable_module_create_module_interface(pool, modname); skinny_endpoint_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_ENDPOINT_INTERFACE); skinny_endpoint_interface->interface_name = "skinny"; skinny_endpoint_interface->io_routines = &skinny_io_routines; skinny_endpoint_interface->state_handler = &skinny_state_handlers; SWITCH_ADD_API(api_interface, "skinny", "Skinny Controls", skinny_function, " "); switch_console_set_complete("add skinny help"); switch_console_set_complete("add skinny status profile ::skinny::list_profiles"); switch_console_set_complete("add skinny status profile ::skinny::list_profiles device ::skinny::list_devices"); switch_console_set_complete("add skinny profile ::skinny::list_profiles device ::skinny::list_devices send SetRingerMessage ::skinny::list_ring_type ::skinny::list_ring_mode"); switch_console_set_complete("add skinny profile ::skinny::list_profiles device ::skinny::list_devices send SetLampMessage ::skinny::list_stimuli ::skinny::list_stimulus_instances ::skinny::list_stimulus_modes"); switch_console_set_complete("add skinny profile ::skinny::list_profiles device ::skinny::list_devices send SetSpeakerModeMessage ::skinny::list_speaker_mode"); switch_console_set_complete("add skinny profile ::skinny::list_profiles device ::skinny::list_devices send CallState ::skinny::list_call_states ::skinny::list_line_instances ::skinny::list_call_ids"); switch_console_add_complete_func("::skinny::list_profiles", skinny_list_profiles); switch_console_add_complete_func("::skinny::list_devices", skinny_list_devices); switch_console_add_complete_func("::skinny::list_ring_type", skinny_list_ring_type); switch_console_add_complete_func("::skinny::list_ring_mode", skinny_list_ring_mode); switch_console_add_complete_func("::skinny::list_stimuli", skinny_list_stimuli); switch_console_add_complete_func("::skinny::list_stimulus_instances", skinny_list_stimulus_instances); switch_console_add_complete_func("::skinny::list_stimulus_modes", skinny_list_stimulus_modes); switch_console_add_complete_func("::skinny::list_speaker_mode", skinny_list_speaker_mode); switch_console_add_complete_func("::skinny::list_call_states", skinny_list_call_states); switch_console_add_complete_func("::skinny::list_line_instances", skinny_list_line_instances); switch_console_add_complete_func("::skinny::list_call_ids", skinny_list_call_ids); /* indicate that the module should continue to be loaded */ return SWITCH_STATUS_SUCCESS; } SWITCH_MODULE_RUNTIME_FUNCTION(mod_skinny_runtime) { switch_status_t status = SWITCH_STATUS_SUCCESS; switch_hash_index_t *hi; void *val; skinny_profile_t *profile; /* launch listeners */ for (hi = switch_hash_first(NULL, globals.profile_hash); hi; hi = switch_hash_next(hi)) { switch_hash_this(hi, NULL, NULL, &val); profile = (skinny_profile_t *) val; status = skinny_socket_create_and_bind(profile); if(status != SWITCH_STATUS_SUCCESS) { return status; } } return status; } SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_skinny_shutdown) { switch_hash_index_t *hi; void *val; skinny_profile_t *profile; int sanity = 0; switch_event_free_subclass(SKINNY_EVENT_REGISTER); switch_event_unbind(&globals.heartbeat_node); globals.running = 0; /* kill listeners */ walk_listeners(kill_listener, NULL); /* close sockets */ for (hi = switch_hash_first(NULL, globals.profile_hash); hi; hi = switch_hash_next(hi)) { switch_hash_this(hi, NULL, NULL, &val); profile = (skinny_profile_t *) val; close_socket(&profile->sock, profile); while (profile->listener_threads) { switch_yield(100000); walk_listeners(kill_listener, NULL); if (++sanity >= 200) { break; } } } switch_safe_free(globals.codec_string); switch_safe_free(globals.codec_rates_string); return SWITCH_STATUS_SUCCESS; } /* For Emacs: * Local Variables: * mode:c * indent-tabs-mode:t * tab-width:4 * c-basic-offset:4 * End: * For VIM: * vim:set softtabstop=4 shiftwidth=4 tabstop=4: */