From 0e8a26f840399a88109b82a76026a628436328ae Mon Sep 17 00:00:00 2001 From: Rupa Schomaker Date: Thu, 1 Apr 2010 21:31:14 -0500 Subject: [PATCH] sync changes in branch rupa_limit --- Makefile.am | 2 + build/modules.conf.in | 3 +- src/include/switch.h | 1 + src/include/switch_limit.h | 127 ++ src/include/switch_loadable_module.h | 21 + src/include/switch_module_interfaces.h | 22 + src/include/switch_types.h | 4 +- .../applications/mod_commands/mod_commands.c | 107 +- .../mod_db.2008.vcproj} | 6 +- src/mod/applications/mod_db/mod_db.c | 663 ++++++++ .../applications/mod_dptools/mod_dptools.c | 136 ++ .../mod_hash/mod_hash.2008.vcproj | 283 ++++ src/mod/applications/mod_hash/mod_hash.c | 506 ++++++ src/mod/applications/mod_lcr/mod_lcr.c | 35 +- src/mod/applications/mod_limit/mod_limit.c | 1350 ----------------- .../applications/mod_limit/mod_limit.vcproj | 153 -- src/switch_ivr_originate.c | 2 + src/switch_limit.c | 222 +++ src/switch_loadable_module.c | 68 +- 19 files changed, 2165 insertions(+), 1546 deletions(-) create mode 100644 src/include/switch_limit.h rename src/mod/applications/{mod_limit/mod_limit.2008.vcproj => mod_db/mod_db.2008.vcproj} (93%) create mode 100644 src/mod/applications/mod_db/mod_db.c create mode 100644 src/mod/applications/mod_hash/mod_hash.2008.vcproj create mode 100644 src/mod/applications/mod_hash/mod_hash.c mode change 100755 => 100644 src/mod/applications/mod_lcr/mod_lcr.c delete mode 100644 src/mod/applications/mod_limit/mod_limit.c delete mode 100644 src/mod/applications/mod_limit/mod_limit.vcproj create mode 100755 src/switch_limit.c diff --git a/Makefile.am b/Makefile.am index a760bb721b..2ef6175cac 100644 --- a/Makefile.am +++ b/Makefile.am @@ -173,6 +173,7 @@ library_include_HEADERS = \ libs/libteletone/src/libteletone_detect.h \ libs/libteletone/src/libteletone_generate.h \ libs/libteletone/src/libteletone.h \ + src/include/switch_limit.h \ src/include/switch_odbc.h nodist_libfreeswitch_la_SOURCES = \ @@ -228,6 +229,7 @@ libfreeswitch_la_SOURCES = \ src/switch_config.c \ src/switch_time.c \ src/switch_odbc.c \ + src/switch_limit.c \ src/g711.c \ src/switch_pcm.c \ src/switch_profile.c\ diff --git a/build/modules.conf.in b/build/modules.conf.in index 5a914bbc7d..8cb6d2a333 100644 --- a/build/modules.conf.in +++ b/build/modules.conf.in @@ -9,10 +9,11 @@ applications/mod_enum #applications/mod_osp applications/mod_fifo #applications/mod_curl +applications/mod_db +applications/mod_hash applications/mod_voicemail #applications/mod_directory #applications/mod_lcr -applications/mod_limit applications/mod_expr applications/mod_esf #applications/mod_easyroute diff --git a/src/include/switch.h b/src/include/switch.h index 275e51fdac..7143c61d91 100644 --- a/src/include/switch.h +++ b/src/include/switch.h @@ -133,6 +133,7 @@ #include "switch_nat.h" #include "switch_odbc.h" #include "switch_json.h" +#include "switch_limit.h" #include diff --git a/src/include/switch_limit.h b/src/include/switch_limit.h new file mode 100644 index 0000000000..c63ac7fb57 --- /dev/null +++ b/src/include/switch_limit.h @@ -0,0 +1,127 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2010, 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): + * + * Rupa Schomaker + * + * switch_limit.h - Limit generic implementations + * + */ + + + /*! + \defgroup limit1 LIMIT code + \ingroup core1 + \{ +*/ +#ifndef _SWITCH_LIMIT_H +#define _SWITCH_LIMIT_H + +SWITCH_BEGIN_EXTERN_C + +/*! + \brief Initilize the LIMIT Core System + \param pool the memory pool to use for long term allocations + \note Generally called by the core_init +*/ +SWITCH_DECLARE(void) switch_limit_init(switch_memory_pool_t *pool); + +/*! + \brief Increment resource. + \param backend to use + \param realm + \param resource + \param max - 0 means no limit, just count + \param interval - 0 means no interval + \return true/false - true ok, false over limit +*/ +SWITCH_DECLARE(switch_status_t) switch_limit_incr(const char *backend, switch_core_session_t *session, const char *realm, const char *resource, const int max, const int interval); + +/*! + \brief Release resource. + \param backend to use + \param realm + \param resource + \return true/false - true ok, false over limit +*/ +SWITCH_DECLARE(switch_status_t) switch_limit_release(const char *backend, switch_core_session_t *session, const char *realm, const char *resource); + +/*! + \brief get usage count for resource + \param backend to use + \param realm + \param resource + \param rcount - output paramter, rate counter +*/ +SWITCH_DECLARE(int) switch_limit_usage(const char *backend, const char *realm, const char *resource, uint32_t *rcount); + +/*! + \brief reset all usage counters + \param backend to use +*/ +SWITCH_DECLARE(switch_status_t) switch_limit_reset(const char *backend); + +/*! + \brief fire event for limit usage + \param backend to use + \param realm + \param resource + \param usage + \param rate + \param max + \param ratemax +*/ +SWITCH_DECLARE(void) switch_limit_fire_event(const char *backend, const char *realm, const char *resource, uint32_t usage, uint32_t rate, uint32_t max, uint32_t ratemax); + +/*! + \brief retrieve arbitrary status information + \param backend to use + \note caller must free returned value +*/ +SWITCH_DECLARE(char *) switch_limit_status(const char *backend); + +/*! callback to init a backend */ +#define SWITCH_LIMIT_INCR(name) static switch_status_t name (switch_core_session_t *session, const char *realm, const char *resource, const int max, const int interval) +#define SWITCH_LIMIT_RELEASE(name) static switch_status_t name (switch_core_session_t *session, const char *realm, const char *resource) +#define SWITCH_LIMIT_USAGE(name) static int name (const char *realm, const char *resource, uint32_t *rcount) +#define SWITCH_LIMIT_RESET(name) static switch_status_t name (void) +#define SWITCH_LIMIT_STATUS(name) static char * name (void) + +#define LIMIT_IGNORE_TRANSFER_VARIABLE "limit_ignore_transfer" +#define LIMIT_BACKEND_VARIABLE "limit_backend" +#define LIMIT_EVENT_USAGE "limit::usage" +#define LIMIT_DEF_XFER_EXTEN "limit_exceeded" + +SWITCH_END_EXTERN_C +#endif +/* 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: + */ diff --git a/src/include/switch_loadable_module.h b/src/include/switch_loadable_module.h index 7b9457ea2b..946bd97f86 100644 --- a/src/include/switch_loadable_module.h +++ b/src/include/switch_loadable_module.h @@ -81,6 +81,8 @@ SWITCH_BEGIN_EXTERN_C switch_asr_interface_t *asr_interface; /*! the table of management interfaces the module has implmented */ switch_management_interface_t *management_interface; + /*! the table of limit interfaces the module has implmented */ + switch_limit_interface_t *limit_interface; switch_thread_rwlock_t *rwlock; int refs; switch_memory_pool_t *pool; @@ -204,6 +206,13 @@ SWITCH_DECLARE(switch_say_interface_t *) switch_loadable_module_get_say_interfac */ SWITCH_DECLARE(switch_management_interface_t *) switch_loadable_module_get_management_interface(const char *relative_oid); +/*! + \brief Retrieve the limit interface by it's registered name + \param name the name of the limit interface + \return the desired limit interface + */ +SWITCH_DECLARE(switch_limit_interface_t *) switch_loadable_module_get_limit_interface(const char *name); + /*! \brief Retrieve the list of loaded codecs into an array \param array the array to populate @@ -315,6 +324,18 @@ SWITCH_MOD_DECLARE(switch_status_t) switch_module_shutdown(void); break; \ } +#define SWITCH_ADD_LIMIT(limit_int, int_name, incrptr, releaseptr, usageptr, resetptr, statusptr) \ + for (;;) { \ + limit_int = (switch_limit_interface_t *)switch_loadable_module_create_interface(*module_interface, SWITCH_LIMIT_INTERFACE); \ + limit_int->incr = incrptr; \ + limit_int->release = releaseptr; \ + limit_int->usage = usageptr; \ + limit_int->reset = resetptr; \ + limit_int->status = statusptr; \ + limit_int->interface_name = int_name; \ + break; \ + } + SWITCH_DECLARE(uint32_t) switch_core_codec_next_id(void); #define SWITCH_ADD_CODEC(codec_int, int_name) \ diff --git a/src/include/switch_module_interfaces.h b/src/include/switch_module_interfaces.h index 642d1639f9..64131378de 100644 --- a/src/include/switch_module_interfaces.h +++ b/src/include/switch_module_interfaces.h @@ -512,6 +512,28 @@ struct switch_management_interface { struct switch_management_interface *next; }; +/*! \brief Abstract interface to a limit module */ +struct switch_limit_interface { + /*! name of the interface */ + const char *interface_name; + /*! increment */ + switch_status_t (*incr) (switch_core_session_t *session, const char *realm, const char *resource, const int max, const int interval); + /*! release */ + switch_status_t (*release) (switch_core_session_t *session, const char *realm, const char *resource); + /*! usage for resource */ + int (*usage) (const char *realm, const char *resource, uint32_t *rcount); + /*! reset counters */ + switch_status_t (*reset) (void); + /*! freform status */ + char * (*status) (void); + /* internal */ + switch_thread_rwlock_t *rwlock; + int refs; + switch_mutex_t *reflock; + switch_loadable_module_interface_t *parent; + struct switch_limit_interface *next; +}; + /*! \brief Abstract interface to a directory module */ struct switch_directory_interface { /*! the name of the interface */ diff --git a/src/include/switch_types.h b/src/include/switch_types.h index 74d5920b24..e65b3b78ce 100644 --- a/src/include/switch_types.h +++ b/src/include/switch_types.h @@ -267,7 +267,8 @@ typedef enum { SWITCH_CHAT_INTERFACE, SWITCH_SAY_INTERFACE, SWITCH_ASR_INTERFACE, - SWITCH_MANAGEMENT_INTERFACE + SWITCH_MANAGEMENT_INTERFACE, + SWITCH_LIMIT_INTERFACE } switch_module_interface_name_t; typedef enum { @@ -1593,6 +1594,7 @@ typedef struct switch_chat_interface switch_chat_interface_t; typedef struct switch_management_interface switch_management_interface_t; typedef struct switch_core_port_allocator switch_core_port_allocator_t; typedef struct switch_media_bug switch_media_bug_t; +typedef struct switch_limit_interface switch_limit_interface_t; struct switch_console_callback_match_node { char *val; diff --git a/src/mod/applications/mod_commands/mod_commands.c b/src/mod/applications/mod_commands/mod_commands.c index 00f2ec6eab..ce2fe0c72b 100644 --- a/src/mod/applications/mod_commands/mod_commands.c +++ b/src/mod/applications/mod_commands/mod_commands.c @@ -3358,7 +3358,7 @@ SWITCH_STANDARD_API(alias_function) return SWITCH_STATUS_SUCCESS; } -#define SHOW_SYNTAX "codec|endpoint|application|api|dialplan|file|timer|calls [count]|channels [count|like ]|distinct_channels|aliases|complete|chat|management|modules|nat_map|say|interfaces|interface_types|tasks" +#define SHOW_SYNTAX "codec|endpoint|application|api|dialplan|file|timer|calls [count]|channels [count|like ]|distinct_channels|aliases|complete|chat|management|modules|nat_map|say|interfaces|interface_types|tasks|limits" SWITCH_STANDARD_API(show_function) { char sql[1024]; @@ -3421,6 +3421,7 @@ SWITCH_STANDARD_API(show_function) !strncasecmp(command, "file", 4) || !strncasecmp(command, "timer", 5) || !strncasecmp(command, "chat", 4) || + !strncasecmp(command, "limit", 5) || !strncasecmp(command, "say", 3) || !strncasecmp(command, "management", 10) || !strncasecmp(command, "endpoint", 8)) { if (end_of(command) == 's') { end_of(command) = '\0'; @@ -4222,6 +4223,107 @@ SWITCH_STANDARD_API(sql_escape) return SWITCH_STATUS_SUCCESS; } +/* LIMIT Stuff */ +#define LIMIT_USAGE_USAGE " [rate]" +SWITCH_STANDARD_API(limit_usage_function) +{ + int argc = 0; + char *argv[5] = { 0 }; + char *mydata = NULL; + uint32_t count = 0; + uint32_t rcount = 0; + switch_bool_t dorate = SWITCH_FALSE; + + if (!zstr(cmd)) { + mydata = strdup(cmd); + switch_assert(mydata); + argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); + } + + if (argc < 3) { + stream->write_function(stream, "USAGE: limit_usage %s\n", LIMIT_USAGE_USAGE); + goto end; + } + + if (argc > 3) { + if (!strcasecmp("rate", argv[3])) { + dorate = SWITCH_TRUE; + } + } + + count = switch_limit_usage(argv[0], argv[1], argv[2], &rcount); + + if (dorate == SWITCH_TRUE) { + stream->write_function(stream, "%d/%d", count, rcount); + } else { + stream->write_function(stream, "%d", count); + } + +end: + switch_safe_free(mydata); + + return SWITCH_STATUS_SUCCESS; +} + +#define LIMIT_STATUS_USAGE "" +SWITCH_STANDARD_API(limit_status_function) +{ + int argc = 0; + char *argv[2] = { 0 }; + char *mydata = NULL; + char *ret = NULL; + + if (!zstr(cmd)) { + mydata = strdup(cmd); + switch_assert(mydata); + argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); + } + + if (argc < 1) { + stream->write_function(stream, "USAGE: limit_status %s\n", LIMIT_STATUS_USAGE); + goto end; + } + + ret = switch_limit_status(argv[0]); + + stream->write_function(stream, "%s", ret); + +end: + switch_safe_free(mydata); + switch_safe_free(ret); + + return SWITCH_STATUS_SUCCESS; +} + +#define LIMIT_RESET_USAGE "" +SWITCH_STANDARD_API(limit_reset_function) +{ + int argc = 0; + char *argv[2] = { 0 }; + char *mydata = NULL; + switch_status_t ret = SWITCH_STATUS_SUCCESS; + + if (!zstr(cmd)) { + mydata = strdup(cmd); + switch_assert(mydata); + argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); + } + + if (argc < 1) { + stream->write_function(stream, "USAGE: limit_reset %s\n", LIMIT_RESET_USAGE); + goto end; + } + + ret = switch_limit_reset(argv[0]); + + stream->write_function(stream, "%s", (ret == SWITCH_STATUS_SUCCESS) ? "+OK" : "-ERR"); + +end: + switch_safe_free(mydata); + + return SWITCH_STATUS_SUCCESS; +} + SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_commands_shutdown) { int x; @@ -4310,6 +4412,9 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_commands_load) SWITCH_ADD_API(commands_api_interface, "hupall", "hupall", hupall_api_function, " [ ]"); SWITCH_ADD_API(commands_api_interface, "in_group", "determine if a user is in a group", in_group_function, "[@] "); SWITCH_ADD_API(commands_api_interface, "is_lan_addr", "see if an ip is a lan addr", lan_addr_function, ""); + SWITCH_ADD_API(commands_api_interface, "limit_usage", "Gets the usage count of a limited resource", limit_usage_function, " "); + SWITCH_ADD_API(commands_api_interface, "limit_status", "Gets the status of a limit backend", limit_status_function, ""); + SWITCH_ADD_API(commands_api_interface, "limit_reset", "Reset the counters of a limit backend", limit_reset_function, ""); SWITCH_ADD_API(commands_api_interface, "load", "Load Module", load_function, LOAD_SYNTAX); SWITCH_ADD_API(commands_api_interface, "log", "Log", log_function, LOG_SYNTAX); SWITCH_ADD_API(commands_api_interface, "md5", "md5", md5_function, ""); diff --git a/src/mod/applications/mod_limit/mod_limit.2008.vcproj b/src/mod/applications/mod_db/mod_db.2008.vcproj similarity index 93% rename from src/mod/applications/mod_limit/mod_limit.2008.vcproj rename to src/mod/applications/mod_db/mod_db.2008.vcproj index 3273279e1b..ae523ea124 100644 --- a/src/mod/applications/mod_limit/mod_limit.2008.vcproj +++ b/src/mod/applications/mod_db/mod_db.2008.vcproj @@ -2,9 +2,9 @@ @@ -274,7 +274,7 @@ diff --git a/src/mod/applications/mod_db/mod_db.c b/src/mod/applications/mod_db/mod_db.c new file mode 100644 index 0000000000..c5dfc11fb4 --- /dev/null +++ b/src/mod/applications/mod_db/mod_db.c @@ -0,0 +1,663 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2010, 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 + * Ken Rice + * Bret McDanel + * Rupa Schomaker + * + * mod_db.c -- Implements simple db API, group support, and limit db backend + * + */ + +#include + +SWITCH_MODULE_LOAD_FUNCTION(mod_db_load); +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_db_shutdown); +SWITCH_MODULE_DEFINITION(mod_db, mod_db_load, mod_db_shutdown, NULL); + +static struct { + switch_memory_pool_t *pool; + char hostname[256]; + char *dbname; + char *odbc_dsn; + char *odbc_user; + char *odbc_pass; + switch_mutex_t *mutex; + switch_mutex_t *db_hash_mutex; + switch_hash_t *db_hash; +} globals; + +typedef struct { + uint32_t total_usage; + uint32_t rate_usage; + time_t last_check; +} limit_hash_item_t; + +struct callback { + char *buf; + size_t len; + int matches; +}; + +typedef struct callback callback_t; + +static int sql2str_callback(void *pArg, int argc, char **argv, char **columnNames) +{ + callback_t *cbt = (callback_t *) pArg; + + switch_copy_string(cbt->buf, argv[0], cbt->len); + cbt->matches++; + return 0; +} + +static char limit_sql[] = + "CREATE TABLE limit_data (\n" + " hostname VARCHAR(255),\n" " realm VARCHAR(255),\n" " id VARCHAR(255),\n" " uuid VARCHAR(255)\n" ");\n"; + +static char db_sql[] = + "CREATE TABLE db_data (\n" + " hostname VARCHAR(255),\n" " realm VARCHAR(255),\n" " data_key VARCHAR(255),\n" " data VARCHAR(255)\n" ");\n"; + +static char group_sql[] = + "CREATE TABLE group_data (\n" " hostname VARCHAR(255),\n" " groupname VARCHAR(255),\n" " url VARCHAR(255)\n" ");\n"; + + +switch_cache_db_handle_t *limit_get_db_handle(void) +{ + switch_cache_db_connection_options_t options = { {0} }; + switch_cache_db_handle_t *dbh = NULL; + + if (!zstr(globals.odbc_dsn)) { + options.odbc_options.dsn = globals.odbc_dsn; + options.odbc_options.user = globals.odbc_user; + options.odbc_options.pass = globals.odbc_pass; + + if (switch_cache_db_get_db_handle(&dbh, SCDB_TYPE_ODBC, &options) != SWITCH_STATUS_SUCCESS) + dbh = NULL; + return dbh; + } else { + options.core_db_options.db_path = globals.dbname; + if (switch_cache_db_get_db_handle(&dbh, SCDB_TYPE_CORE_DB, &options) != SWITCH_STATUS_SUCCESS) + dbh = NULL; + return dbh; + } +} + + +static switch_status_t limit_execute_sql(char *sql) +{ + switch_cache_db_handle_t *dbh = NULL; + switch_status_t status = SWITCH_STATUS_FALSE; + + if (!(dbh = limit_get_db_handle())) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Opening DB\n"); + goto end; + } + + status = switch_cache_db_execute_sql(dbh, sql, NULL); + + end: + + switch_cache_db_release_db_handle(&dbh); + + return status; +} + + +static switch_bool_t limit_execute_sql_callback(char *sql, switch_core_db_callback_func_t callback, void *pdata) +{ + switch_bool_t ret = SWITCH_FALSE; + char *errmsg = NULL; + switch_cache_db_handle_t *dbh = NULL; + + if (!(dbh = limit_get_db_handle())) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Opening DB\n"); + goto end; + } + + switch_cache_db_execute_sql_callback(dbh, sql, callback, pdata, &errmsg); + + if (errmsg) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SQL ERR: [%s] %s\n", sql, errmsg); + free(errmsg); + } + + end: + + switch_cache_db_release_db_handle(&dbh); + + return ret; +} + +static char * limit_execute_sql2str(char *sql, char *str, size_t len) +{ + callback_t cbt = { 0 }; + + cbt.buf = str; + cbt.len = len; + + limit_execute_sql_callback(sql, sql2str_callback, &cbt); + + return cbt.buf; +} + +/* \brief Enforces limit restrictions + * \param session current session + * \param realm limit realm + * \param id limit id + * \param max maximum count + * \return SWITCH_STATUS_SUCCESS if the access is allowed + */ +SWITCH_LIMIT_INCR(limit_incr_sql) +{ + switch_channel_t *channel = switch_core_session_get_channel(session); + int got = 0; + char *sql = NULL; + char gotstr[128]; + switch_status_t status = SWITCH_STATUS_SUCCESS; + + // check max! WTF + + switch_mutex_lock(globals.mutex); + + switch_channel_set_variable(channel, "limit_realm", realm); + switch_channel_set_variable(channel, "limit_id", resource); + switch_channel_set_variable(channel, "limit_max", switch_core_session_sprintf(session, "%d", max)); + + sql = switch_mprintf("select count(hostname) from limit_data where realm='%q' and id='%q';", realm, resource); + limit_execute_sql2str(sql, gotstr, 128); + switch_safe_free(sql); + got = atoi(gotstr); + + if (max < 0) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Usage for %s_%s is now %d\n", realm, resource, got + 1); + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Usage for %s_%s is now %d/%d\n", realm, resource, got + 1, max); + } + + if (max >= 0 && got + 1 > max) { + status = SWITCH_STATUS_GENERR; + goto done; + } + + sql = + switch_mprintf("insert into limit_data (hostname, realm, id, uuid) values('%q','%q','%q','%q');", globals.hostname, realm, resource, + switch_core_session_get_uuid(session)); + limit_execute_sql(sql); + switch_safe_free(sql); + + { + const char *susage = switch_core_session_sprintf(session, "%d", ++got); + + switch_channel_set_variable(channel, "limit_usage", susage); + switch_channel_set_variable(channel, switch_core_session_sprintf(session, "limit_usage_%s_%s", realm, resource), susage); + } + switch_limit_fire_event("sql", realm, resource, got, 0, max, 0); + + done: + switch_mutex_unlock(globals.mutex); + return status; +} + +SWITCH_LIMIT_RELEASE(limit_release_sql) +{ + char *sql = NULL; + + if (realm == NULL && resource == NULL) { + sql = switch_mprintf("delete from limit_data where uuid='%q'", switch_core_session_get_uuid(session)); + } else { + sql = switch_mprintf("delete from limit_data where uuid='%q' and realm='%q' and id = '%q'", switch_core_session_get_uuid(session), realm, resource); + } + limit_execute_sql(sql); + switch_safe_free(sql); + + return SWITCH_STATUS_SUCCESS; +} + +SWITCH_LIMIT_USAGE(limit_usage_sql) +{ + char usagestr[128] = ""; + int usage = 0; + char *sql = NULL; + + sql = switch_mprintf("select count(hostname) from limit_data where realm='%q' and id='%q'", realm, resource); + limit_execute_sql2str(sql, usagestr, sizeof(usagestr)); + switch_safe_free(sql); + usage = atoi(usagestr); + + return usage; +} + +SWITCH_LIMIT_RESET(limit_reset_sql) +{ + char *sql = NULL; + sql = switch_mprintf("delete from limit_data where hostname='%q';", globals.hostname); + limit_execute_sql(sql); + switch_safe_free(sql); + + return SWITCH_STATUS_SUCCESS; +} + +SWITCH_LIMIT_STATUS(limit_status_sql) +{ + char count[128] = ""; + char *ret = NULL; + char *sql = NULL; + + sql = switch_mprintf("select count(hostname) from limit_data where hostname='%q'", globals.hostname); + limit_execute_sql2str(sql, count, sizeof(count)); + switch_safe_free(sql); + ret = switch_mprintf("Tracking %s resources for hostname %s.", count, globals.hostname); + return ret; +} + +/* INIT / Config */ + +static switch_xml_config_string_options_t limit_config_dsn = { NULL, 0, "[^:]+:[^:]+:.+" }; + +static switch_xml_config_item_t config_settings[] = { + SWITCH_CONFIG_ITEM("odbc-dsn", SWITCH_CONFIG_STRING, 0, &globals.odbc_dsn, NULL, &limit_config_dsn, + "dsn:username:password", "If set, the ODBC DSN used by the limit and db applications"), + SWITCH_CONFIG_ITEM_END() +}; + +static switch_status_t do_config() +{ + switch_cache_db_handle_t *dbh = NULL; + switch_status_t status = SWITCH_STATUS_SUCCESS; + char *sql = NULL; + + limit_config_dsn.pool = globals.pool; + + if (switch_xml_config_parse_module_settings("db.conf", SWITCH_FALSE, config_settings) != SWITCH_STATUS_SUCCESS) { + return SWITCH_STATUS_TERM; + } + + if (globals.odbc_dsn) { + if ((globals.odbc_user = strchr(globals.odbc_dsn, ':'))) { + *globals.odbc_user++ = '\0'; + if ((globals.odbc_pass = strchr(globals.odbc_user, ':'))) { + *globals.odbc_pass++ = '\0'; + } + } + + if (!(dbh = limit_get_db_handle())) { + globals.odbc_dsn = globals.odbc_user = globals.odbc_pass; + } + } + + + if (zstr(globals.odbc_dsn)) { + globals.dbname = "call_limit"; + dbh = limit_get_db_handle(); + } + + + if (dbh) { + int x = 0; + char *indexes[] = { + "create index ld_hostname on limit_data (hostname)", + "create index ld_uuid on limit_data (uuid)", + "create index ld_realm on limit_data (realm)", + "create index ld_id on limit_data (id)", + "create index dd_realm on db_data (realm)", + "create index dd_data_key on db_data (data_key)", + "create index gd_groupname on group_data (groupname)", + "create index gd_url on group_data (url)", + NULL + }; + + + + switch_cache_db_test_reactive(dbh, "select * from limit_data", NULL, limit_sql); + switch_cache_db_test_reactive(dbh, "select * from db_data", NULL, db_sql); + switch_cache_db_test_reactive(dbh, "select * from group_data", NULL, group_sql); + + for (x = 0; indexes[x]; x++) { + switch_cache_db_execute_sql(dbh, indexes[x], NULL); + } + + switch_cache_db_release_db_handle(&dbh); + + sql = switch_mprintf("delete from limit_data where hostname='%q';", globals.hostname); + limit_execute_sql(sql); + switch_safe_free(sql); + } + + return status; +} + +/* APP/API STUFF */ + +/* CORE DB STUFF */ + +SWITCH_STANDARD_API(db_api_function) +{ + int argc = 0; + char *argv[4] = { 0 }; + char *mydata = NULL; + char *sql; + + if (!zstr(cmd)) { + mydata = strdup(cmd); + switch_assert(mydata); + argc = switch_separate_string(mydata, '/', argv, (sizeof(argv) / sizeof(argv[0]))); + } + + if (argc < 1 || !argv[0]) { + goto error; + } + + if (!strcasecmp(argv[0], "insert")) { + if (argc < 4) { + goto error; + } + sql = switch_mprintf("delete from db_data where realm='%q' and data_key='%q'", argv[1], argv[2]); + switch_assert(sql); + limit_execute_sql(sql); + switch_safe_free(sql); + sql = + switch_mprintf("insert into db_data (hostname, realm, data_key, data) values('%q','%q','%q','%q');", globals.hostname, argv[1], argv[2], + argv[3]); + switch_assert(sql); + limit_execute_sql(sql); + switch_safe_free(sql); + stream->write_function(stream, "+OK"); + goto done; + } else if (!strcasecmp(argv[0], "delete")) { + if (argc < 2) { + goto error; + } + sql = switch_mprintf("delete from db_data where realm='%q' and data_key='%q'", argv[1], argv[2]); + switch_assert(sql); + limit_execute_sql(sql); + switch_safe_free(sql); + stream->write_function(stream, "+OK"); + goto done; + } else if (!strcasecmp(argv[0], "select")) { + char buf[256] = ""; + if (argc < 3) { + goto error; + } + sql = switch_mprintf("select data from db_data where realm='%q' and data_key='%q'", argv[1], argv[2]); + limit_execute_sql2str(sql, buf, sizeof(buf)); + switch_safe_free(sql); + stream->write_function(stream, "%s", buf); + goto done; + } + + error: + stream->write_function(stream, "!err!"); + + done: + + switch_safe_free(mydata); + return SWITCH_STATUS_SUCCESS; +} + +#define DB_USAGE "[insert|delete]///" +#define DB_DESC "save data" + +SWITCH_STANDARD_APP(db_function) +{ + int argc = 0; + char *argv[4] = { 0 }; + char *mydata = NULL; + char *sql = NULL; + + if (!zstr(data)) { + mydata = switch_core_session_strdup(session, data); + argc = switch_separate_string(mydata, '/', argv, (sizeof(argv) / sizeof(argv[0]))); + } + + if (argc < 3 || !argv[0]) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "USAGE: db %s\n", DB_USAGE); + return; + } + + if (!strcasecmp(argv[0], "insert")) { + if (argc < 4) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "USAGE: db %s\n", DB_USAGE); + return; + } + sql = switch_mprintf("delete from db_data where realm='%q' and data_key='%q'", argv[1], argv[2]); + switch_assert(sql); + limit_execute_sql(sql); + switch_safe_free(sql); + + sql = + switch_mprintf("insert into db_data (hostname, realm, data_key, data) values('%q','%q','%q','%q');", globals.hostname, argv[1], argv[2], + argv[3]); + } else if (!strcasecmp(argv[0], "delete")) { + sql = switch_mprintf("delete from db_data where realm='%q' and data_key='%q'", argv[1], argv[2]); + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "USAGE: db %s\n", DB_USAGE); + return; + } + + if (sql) { + limit_execute_sql(sql); + switch_safe_free(sql); + } +} + +/* GROUP STUFF */ + +static int group_callback(void *pArg, int argc, char **argv, char **columnNames) +{ + callback_t *cbt = (callback_t *) pArg; + switch_snprintf(cbt->buf + strlen(cbt->buf), cbt->len - strlen(cbt->buf), "%s%c", argv[0], *argv[1]); + cbt->matches++; + return 0; +} + +SWITCH_STANDARD_API(group_api_function) +{ + int argc = 0; + char *argv[4] = { 0 }; + char *mydata = NULL; + char *sql; + + if (!zstr(cmd)) { + mydata = strdup(cmd); + argc = switch_separate_string(mydata, ':', argv, (sizeof(argv) / sizeof(argv[0]))); + } + + if (argc < 2 || !argv[0]) { + goto error; + } + + if (!strcasecmp(argv[0], "insert")) { + if (argc < 3) { + goto error; + } + sql = switch_mprintf("delete from group_data where groupname='%q' and url='%q';", argv[1], argv[2]); + switch_assert(sql); + + limit_execute_sql(sql); + switch_safe_free(sql); + sql = switch_mprintf("insert into group_data (hostname, groupname, url) values('%q','%q','%q');", globals.hostname, argv[1], argv[2]); + switch_assert(sql); + limit_execute_sql(sql); + switch_safe_free(sql); + stream->write_function(stream, "+OK"); + goto done; + } else if (!strcasecmp(argv[0], "delete")) { + if (argc < 3) { + goto error; + } + if (!strcmp(argv[2], "*")) { + sql = switch_mprintf("delete from group_data where groupname='%q';", argv[1]); + } else { + sql = switch_mprintf("delete from group_data where groupname='%q' and url='%q';", argv[1], argv[2]); + } + switch_assert(sql); + limit_execute_sql(sql); + switch_safe_free(sql); + stream->write_function(stream, "+OK"); + goto done; + } else if (!strcasecmp(argv[0], "call")) { + char buf[4096] = ""; + char *how = ","; + callback_t cbt = { 0 }; + cbt.buf = buf; + cbt.len = sizeof(buf); + + if (argc > 2) { + if (!strcasecmp(argv[2], "order")) { + how = "|"; + } + } + + sql = switch_mprintf("select url,'%q' from group_data where groupname='%q'", how, argv[1]); + switch_assert(sql); + + limit_execute_sql_callback(sql, group_callback, &cbt); + switch_safe_free(sql); + + *(buf + (strlen(buf) - 1)) = '\0'; + stream->write_function(stream, "%s", buf); + + goto done; + } + + error: + stream->write_function(stream, "!err!"); + + done: + + switch_safe_free(mydata); + return SWITCH_STATUS_SUCCESS; +} + +#define GROUP_USAGE "[insert|delete]::" +#define GROUP_DESC "save data" + +SWITCH_STANDARD_APP(group_function) +{ + int argc = 0; + char *argv[3] = { 0 }; + char *mydata = NULL; + char *sql; + + if (!zstr(data)) { + mydata = switch_core_session_strdup(session, data); + argc = switch_separate_string(mydata, ':', argv, (sizeof(argv) / sizeof(argv[0]))); + } + + if (argc < 3 || !argv[0]) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "USAGE: group %s\n", DB_USAGE); + return; + } + + if (!strcasecmp(argv[0], "insert")) { + sql = switch_mprintf("insert into group_data (hostname, groupname, url) values('%q','%q','%q');", globals.hostname, argv[1], argv[2]); + switch_assert(sql); + limit_execute_sql(sql); + switch_safe_free(sql); + } else if (!strcasecmp(argv[0], "delete")) { + sql = switch_mprintf("delete from group_data where groupname='%q' and url='%q';", argv[1], argv[2]); + switch_assert(sql); + limit_execute_sql(sql); + switch_safe_free(sql); + } +} + +/* INIT/DEINIT STUFF */ + +SWITCH_MODULE_LOAD_FUNCTION(mod_db_load) +{ + switch_status_t status; + switch_application_interface_t *app_interface; + switch_api_interface_t *commands_api_interface; + switch_limit_interface_t *limit_interface; + + memset(&globals, 0, sizeof(&globals)); + gethostname(globals.hostname, sizeof(globals.hostname)); + globals.pool = pool; + + + if ((status = do_config() != SWITCH_STATUS_SUCCESS)) { + return status; + } + + switch_mutex_init(&globals.mutex, SWITCH_MUTEX_NESTED, globals.pool); + switch_mutex_init(&globals.db_hash_mutex, SWITCH_MUTEX_NESTED, globals.pool); + switch_core_hash_init(&globals.db_hash, pool); + + status = switch_event_reserve_subclass(LIMIT_EVENT_USAGE); + if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_INUSE) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't register event subclass \"%s\" (%d)\n", LIMIT_EVENT_USAGE, status); + return SWITCH_STATUS_FALSE; + } + + /* connect my internal structure to the blank pointer passed to me */ + *module_interface = switch_loadable_module_create_module_interface(pool, modname); + + /* register limit interfaces */ + SWITCH_ADD_LIMIT(limit_interface, "sql", limit_incr_sql, limit_release_sql, limit_usage_sql, limit_reset_sql, limit_status_sql); + + SWITCH_ADD_APP(app_interface, "db", "Insert to the db", DB_DESC, db_function, DB_USAGE, SAF_SUPPORT_NOMEDIA); + SWITCH_ADD_APP(app_interface, "group", "Manage a group", GROUP_DESC, group_function, GROUP_USAGE, SAF_SUPPORT_NOMEDIA); + SWITCH_ADD_API(commands_api_interface, "db", "db get/set", db_api_function, "[insert|delete|select]///"); + switch_console_set_complete("add db insert"); + switch_console_set_complete("add db delete"); + switch_console_set_complete("add db select"); + SWITCH_ADD_API(commands_api_interface, "group", "group [insert|delete|call]", group_api_function, "[insert|delete|call]::"); + switch_console_set_complete("add group insert"); + switch_console_set_complete("add group delete"); + switch_console_set_complete("add group call"); + + /* indicate that the module should continue to be loaded */ + return SWITCH_STATUS_SUCCESS; + +} + + +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_db_shutdown) +{ + + switch_xml_config_cleanup(config_settings); + + switch_mutex_destroy(globals.mutex); + switch_mutex_destroy(globals.db_hash_mutex); + + switch_core_hash_destroy(&globals.db_hash); + + 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: + */ diff --git a/src/mod/applications/mod_dptools/mod_dptools.c b/src/mod/applications/mod_dptools/mod_dptools.c index 187fa446ab..70b58f508c 100755 --- a/src/mod/applications/mod_dptools/mod_dptools.c +++ b/src/mod/applications/mod_dptools/mod_dptools.c @@ -3007,6 +3007,140 @@ SWITCH_STANDARD_APP(session_loglevel_function) } } +/* LIMIT STUFF */ +#define LIMIT_USAGE " [[/interval]] [number [dialplan [context]]]" +#define LIMIT_DESC "limit access to a resource and transfer to an extension if the limit is exceeded" +SWITCH_STANDARD_APP(limit_function) +{ + int argc = 0; + char *argv[7] = { 0 }; + char *mydata = NULL; + char *backend = NULL; + char *realm = NULL; + char *id = NULL; + char *xfer_exten = NULL; + int max = -1; + int interval = 0; + switch_channel_t *channel = switch_core_session_get_channel(session); + + /* Parse application data */ + if (!zstr(data)) { + mydata = switch_core_session_strdup(session, data); + argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); + } + + if (argc < 3) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "USAGE: limit %s\n", LIMIT_USAGE); + return; + } + + backend = argv[0]; + realm = argv[1]; + id = argv[2]; + + /* If max is omitted or negative, only act as a counter and skip maximum checks */ + if (argc > 3) { + if (argv[3][0] == '-') { + max = -1; + } else { + char *szinterval = NULL; + if ((szinterval = strchr(argv[3], '/'))) { + *szinterval++ = '\0'; + interval = atoi(szinterval); + } + + max = atoi(argv[3]); + + if (max < 0) { + max = 0; + } + } + } + + if (argc > 4) { + xfer_exten = argv[4]; + } else { + xfer_exten = LIMIT_DEF_XFER_EXTEN; + } + + if (switch_limit_incr(backend, session, realm, id, max, interval) != SWITCH_STATUS_SUCCESS) { + /* Limit exceeded */ + if (*xfer_exten == '!') { + switch_channel_hangup(channel, switch_channel_str2cause(xfer_exten + 1)); + } else { + switch_ivr_session_transfer(session, xfer_exten, argv[5], argv[6]); + } + } +} + + +#define LIMITEXECUTE_USAGE " [[/interval]] [application] [application arguments]" +#define LIMITEXECUTE_DESC "limit access to a resource. the specified application will only be executed if the resource is available" +SWITCH_STANDARD_APP(limit_execute_function) +{ + int argc = 0; + char *argv[6] = { 0 }; + char *mydata = NULL; + char *backend = NULL; + char *realm = NULL; + char *id = NULL; + char *app = NULL; + char *app_arg = NULL; + int max = -1; + int interval = 0; + + /* Parse application data */ + if (!zstr(data)) { + mydata = switch_core_session_strdup(session, data); + argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); + } + + if (argc < 6) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "USAGE: limit_execute %s\n", LIMITEXECUTE_USAGE); + return; + } + + backend = argv[0]; + realm = argv[1]; + id = argv[2]; + + /* Accept '-' as unlimited (act as counter) */ + if (argv[3][0] == '-') { + max = -1; + } else { + char *szinterval = NULL; + + if ((szinterval = strchr(argv[3], '/'))) { + *szinterval++ = '\0'; + interval = atoi(szinterval); + } + + max = atoi(argv[3]); + + if (max < 0) { + max = 0; + } + } + + app = argv[4]; + app_arg = argv[5]; + + if (zstr(app)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Missing application\n"); + return; + } + + if (switch_limit_incr(backend, session, realm, id, max, interval) == SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Executing\n"); + switch_core_session_execute_application(session, app, app_arg); + /* Only release the resource if we are still in CS_EXECUTE */ + if (switch_channel_get_state(switch_core_session_get_channel(session)) == CS_EXECUTE) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "immediately releasing\n"); + switch_limit_release(backend, session, realm, id); + } + } +} + #define SPEAK_DESC "Speak text to a channel via the tts interface" #define DISPLACE_DESC "Displace audio from a file to the channels input" #define SESS_REC_DESC "Starts a background recording of the entire session" @@ -3176,6 +3310,8 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_dptools_load) SAF_NONE); SWITCH_ADD_APP(app_interface, "session_loglevel", "session_loglevel", "session_loglevel", session_loglevel_function, SESSION_LOGLEVEL_SYNTAX, SAF_SUPPORT_NOMEDIA); + SWITCH_ADD_APP(app_interface, "limit", "Limit", LIMIT_DESC, limit_function, LIMIT_USAGE, SAF_SUPPORT_NOMEDIA); + SWITCH_ADD_APP(app_interface, "limit_execute", "Limit", LIMITEXECUTE_USAGE, limit_execute_function, LIMITEXECUTE_USAGE, SAF_SUPPORT_NOMEDIA); SWITCH_ADD_DIALPLAN(dp_interface, "inline", inline_dialplan_hunt); diff --git a/src/mod/applications/mod_hash/mod_hash.2008.vcproj b/src/mod/applications/mod_hash/mod_hash.2008.vcproj new file mode 100644 index 0000000000..32da23d813 --- /dev/null +++ b/src/mod/applications/mod_hash/mod_hash.2008.vcproj @@ -0,0 +1,283 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/mod/applications/mod_hash/mod_hash.c b/src/mod/applications/mod_hash/mod_hash.c new file mode 100644 index 0000000000..6035b0b225 --- /dev/null +++ b/src/mod/applications/mod_hash/mod_hash.c @@ -0,0 +1,506 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2010, 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 + * Ken Rice + * Bret McDanel + * Rupa Schomaker + * + * mod_hash.c -- Hash api, hash backend for limit + * + */ + +#include + +#define LIMIT_HASH_CLEANUP_INTERVAL 900 + +SWITCH_MODULE_LOAD_FUNCTION(mod_hash_load); +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_hash_shutdown); +SWITCH_MODULE_DEFINITION(mod_hash, mod_hash_load, mod_hash_shutdown, NULL); + +/* CORE STUFF */ +static struct { + switch_memory_pool_t *pool; + switch_thread_rwlock_t *limit_hash_rwlock; + switch_hash_t *limit_hash; + switch_thread_rwlock_t *db_hash_rwlock; + switch_hash_t *db_hash; +} globals; + +typedef struct { + uint32_t total_usage; + uint32_t rate_usage; + time_t last_check; + uint32_t interval; +} limit_hash_item_t; + +struct callback { + char *buf; + size_t len; + int matches; +}; + +typedef struct callback callback_t; + +/* HASH STUFF */ +typedef struct { + switch_hash_t *hash; +} limit_hash_private_t; + +/* \brief Enforces limit_hash restrictions + * \param session current session + * \param realm limit realm + * \param id limit id + * \param max maximum count + * \param interval interval for rate limiting + * \return SWITCH_TRUE if the access is allowed, SWITCH_FALSE if it isnt + */ +SWITCH_LIMIT_INCR(limit_incr_hash) +{ + switch_channel_t *channel = switch_core_session_get_channel(session); + char *hashkey = NULL; + switch_bool_t status = SWITCH_STATUS_SUCCESS; + limit_hash_item_t *item = NULL; + time_t now = switch_epoch_time_now(NULL); + limit_hash_private_t *pvt = NULL; + uint8_t increment = 1; + + hashkey = switch_core_session_sprintf(session, "%s_%s", realm, resource); + + switch_thread_rwlock_wrlock(globals.limit_hash_rwlock); + /* Check if that realm+resource has ever been checked */ + if (!(item = (limit_hash_item_t *) switch_core_hash_find(globals.limit_hash, hashkey))) { + /* No, create an empty structure and add it, then continue like as if it existed */ + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG10, "Creating new limit structure: key: %s\n", hashkey); + item = (limit_hash_item_t *) malloc(sizeof(limit_hash_item_t)); + switch_assert(item); + memset(item, 0, sizeof(limit_hash_item_t)); + switch_core_hash_insert(globals.limit_hash, hashkey, item); + } + + /* Did we already run on this channel before? */ + if ((pvt = switch_channel_get_private(channel, "limit_hash"))) { + /* Yes, but check if we did that realm+resource + If we didnt, allow incrementing the counter. + If we did, dont touch it but do the validation anyways + */ + increment = !switch_core_hash_find(pvt->hash, hashkey); + } else { + /* This is the first limit check on this channel, create a hashtable, set our prviate data */ + pvt = (limit_hash_private_t *) switch_core_session_alloc(session, sizeof(limit_hash_private_t)); + memset(pvt, 0, sizeof(limit_hash_private_t)); + switch_core_hash_init(&pvt->hash, switch_core_session_get_pool(session)); + switch_channel_set_private(channel, "limit_hash", pvt); + } + + if (interval > 0) { + item->interval = interval; + if (item->last_check <= (now - interval)) { + item->rate_usage = 1; + item->last_check = now; + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG10, "Usage for %s reset to 1\n", + hashkey); + } else { + /* Always increment rate when its checked as it doesnt depend on the channel */ + item->rate_usage++; + + if ((max >= 0) && (item->rate_usage > (uint32_t) max)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Usage for %s exceeds maximum rate of %d/%ds, now at %d\n", + hashkey, max, interval, item->rate_usage); + status = SWITCH_STATUS_GENERR; + goto end; + } + } + } else if ((max >= 0) && (item->total_usage + increment > (uint32_t) max)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Usage for %s is already at max value (%d)\n", hashkey, item->total_usage); + status = SWITCH_STATUS_GENERR; + goto end; + } + + if (increment) { + item->total_usage++; + + switch_core_hash_insert(pvt->hash, hashkey, item); + + if (max == -1) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Usage for %s is now %d\n", hashkey, item->total_usage); + } else if (interval == 0) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Usage for %s is now %d/%d\n", hashkey, item->total_usage, max); + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Usage for %s is now %d/%d for the last %d seconds\n", hashkey, + item->rate_usage, max, interval); + } + + switch_limit_fire_event("hash", realm, resource, item->total_usage, item->rate_usage, max, max >= 0 ? (uint32_t) max : 0); + } + + /* Save current usage & rate into channel variables so it can be used later in the dialplan, or added to CDR records */ + { + const char *susage = switch_core_session_sprintf(session, "%d", item->total_usage); + const char *srate = switch_core_session_sprintf(session, "%d", item->rate_usage); + + switch_channel_set_variable(channel, "limit_usage", susage); + switch_channel_set_variable(channel, switch_core_session_sprintf(session, "limit_usage_%s", hashkey), susage); + + switch_channel_set_variable(channel, "limit_rate", srate); + switch_channel_set_variable(channel, switch_core_session_sprintf(session, "limit_rate_%s", hashkey), srate); + } + + end: + switch_thread_rwlock_unlock(globals.limit_hash_rwlock); + return status; +} + +/* !\brief Determines whether a given entry is ready to be removed. */ +SWITCH_HASH_DELETE_FUNC(limit_hash_cleanup_delete_callback) { + limit_hash_item_t *item = (limit_hash_item_t *) val; + time_t now = switch_epoch_time_now(NULL); + + /* reset to 0 if window has passed so we can clean it up */ + if (item->rate_usage > 0 && (item->last_check <= (now - item->interval))) { + item->rate_usage = 0; + } + + if (item->total_usage == 0 && item->rate_usage == 0) { + /* Noone is using this item anymore */ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Freeing limit item: %s\n", (const char *) key); + + free(item); + return SWITCH_TRUE; + } + + return SWITCH_FALSE; +} + +/* !\brief Periodically checks for unused limit entries and frees them */ +SWITCH_STANDARD_SCHED_FUNC(limit_hash_cleanup_callback) +{ + switch_thread_rwlock_wrlock(globals.limit_hash_rwlock); + switch_core_hash_delete_multi(globals.limit_hash, limit_hash_cleanup_delete_callback, NULL); + switch_thread_rwlock_unlock(globals.limit_hash_rwlock); + + task->runtime = switch_epoch_time_now(NULL) + LIMIT_HASH_CLEANUP_INTERVAL; +} + +/* !\brief Releases usage of a limit_hash-controlled ressource */ +SWITCH_LIMIT_RELEASE(limit_release_hash) +{ + switch_channel_t *channel = switch_core_session_get_channel(session); + limit_hash_private_t *pvt = switch_channel_get_private(channel, "limit_hash"); + limit_hash_item_t *item = NULL; + switch_hash_index_t *hi; + char *hashkey = NULL; + + if (!pvt || !pvt->hash) { + return SWITCH_STATUS_SUCCESS; + } + + switch_thread_rwlock_wrlock(globals.limit_hash_rwlock); + + /* clear for uuid */ + if (realm == NULL && resource == NULL) { + /* Loop through the channel's hashtable which contains mapping to all the limit_hash_item_t referenced by that channel */ + while ((hi = switch_hash_first(NULL, pvt->hash))) { + void *val = NULL; + const void *key; + switch_ssize_t keylen; + limit_hash_item_t *item = NULL; + + switch_hash_this(hi, &key, &keylen, &val); + + item = (limit_hash_item_t *) val; + item->total_usage--; + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Usage for %s is now %d\n", (const char *) key, item->total_usage); + + if (item->total_usage == 0 && item->rate_usage == 0) { + /* Noone is using this item anymore */ + switch_core_hash_delete(globals.limit_hash, (const char *) key); + free(item); + } + + switch_core_hash_delete(pvt->hash, (const char *) key); + } + } else { + hashkey = switch_core_session_sprintf(session, "%s_%s", realm, resource); + + if ((item = (limit_hash_item_t *) switch_core_hash_find(pvt->hash, hashkey))) { + item->total_usage--; + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Usage for %s is now %d\n", (const char *) hashkey, item->total_usage); + + switch_core_hash_delete(pvt->hash, hashkey); + + if (item->total_usage == 0 && item->rate_usage == 0) { + /* Noone is using this item anymore */ + switch_core_hash_delete(globals.limit_hash, (const char *) hashkey); + free(item); + } + } + } + + switch_thread_rwlock_unlock(globals.limit_hash_rwlock); + + return SWITCH_STATUS_SUCCESS; +} + +SWITCH_LIMIT_USAGE(limit_usage_hash) +{ + char *hash_key = NULL; + limit_hash_item_t *item = NULL; + int count = 0; + + switch_thread_rwlock_rdlock(globals.limit_hash_rwlock); + + hash_key = switch_mprintf("%s_%s", realm, resource); + + if ((item = switch_core_hash_find(globals.limit_hash, hash_key))) { + count = item->total_usage; + *rcount = item->rate_usage; + } + + switch_safe_free(hash_key); + switch_thread_rwlock_unlock(globals.limit_hash_rwlock); + + return count; +} + +SWITCH_LIMIT_RESET(limit_reset_hash) +{ + return SWITCH_STATUS_GENERR; +} + +SWITCH_LIMIT_STATUS(limit_status_hash) +{ + /* + switch_hash_index_t *hi = NULL; + int count = 0; + char *ret = NULL; + + switch_thread_rwlock_rdlock(globals.limit_hash_rwlock); + + for (hi = switch_hash_first(NULL, globals.limit_hash); hi; switch_hash_next(hi)) { + count++; + } + + switch_thread_rwlock_unlock(globals.limit_hash_rwlock); + + ret = switch_mprintf("There are %d elements being tracked.", count); + return ret; + */ + return strdup("-ERR not supported yet (locking problems)."); +} + +/* APP/API STUFF */ + +/* CORE HASH STUFF */ + +#define HASH_USAGE "[insert|delete]///" +#define HASH_DESC "save data" + +SWITCH_STANDARD_APP(hash_function) +{ + int argc = 0; + char *argv[4] = { 0 }; + char *mydata = NULL; + char *hash_key = NULL; + char *value = NULL; + + switch_thread_rwlock_wrlock(globals.db_hash_rwlock); + + if (!zstr(data)) { + mydata = strdup(data); + switch_assert(mydata); + argc = switch_separate_string(mydata, '/', argv, (sizeof(argv) / sizeof(argv[0]))); + } + + if (argc < 3 || !argv[0]) { + goto usage; + } + + hash_key = switch_mprintf("%s_%s", argv[1], argv[2]); + + if (!strcasecmp(argv[0], "insert")) { + if (argc < 4) { + goto usage; + } + if ((value = switch_core_hash_find(globals.db_hash, hash_key))) { + free(value); + switch_core_hash_delete(globals.db_hash, hash_key); + } + value = strdup(argv[3]); + switch_assert(value); + switch_core_hash_insert(globals.db_hash, hash_key, value); + } else if (!strcasecmp(argv[0], "delete")) { + if ((value = switch_core_hash_find(globals.db_hash, hash_key))) { + switch_safe_free(value); + switch_core_hash_delete(globals.db_hash, hash_key); + } + } else { + goto usage; + } + + goto done; + + usage: + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "USAGE: hash %s\n", HASH_USAGE); + + done: + switch_thread_rwlock_unlock(globals.db_hash_rwlock); + switch_safe_free(mydata); + switch_safe_free(hash_key); +} + +#define HASH_API_USAGE "insert|select|delete/realm/key[/value]" +SWITCH_STANDARD_API(hash_api_function) +{ + int argc = 0; + char *argv[4] = { 0 }; + char *mydata = NULL; + char *value = NULL; + char *hash_key = NULL; + + if (!zstr(cmd)) { + mydata = strdup(cmd); + switch_assert(mydata); + argc = switch_separate_string(mydata, '/', argv, (sizeof(argv) / sizeof(argv[0]))); + } + + if (argc < 3 || !argv[0]) { + goto usage; + } + + hash_key = switch_mprintf("%s_%s", argv[1], argv[2]); + + if (!strcasecmp(argv[0], "insert")) { + if (argc < 4) { + goto usage; + } + switch_thread_rwlock_wrlock(globals.db_hash_rwlock); + if ((value = switch_core_hash_find(globals.db_hash, hash_key))) { + switch_safe_free(value); + switch_core_hash_delete(globals.db_hash, hash_key); + } + value = strdup(argv[3]); + switch_assert(value); + switch_core_hash_insert(globals.db_hash, hash_key, value); + stream->write_function(stream, "+OK\n"); + } else if (!strcasecmp(argv[0], "delete")) { + switch_thread_rwlock_wrlock(globals.db_hash_rwlock); + if ((value = switch_core_hash_find(globals.db_hash, hash_key))) { + switch_safe_free(value); + switch_core_hash_delete(globals.db_hash, hash_key); + stream->write_function(stream, "+OK\n"); + } else { + stream->write_function(stream, "-ERR Not found\n"); + } + } else if (!strcasecmp(argv[0], "select")) { + switch_thread_rwlock_rdlock(globals.db_hash_rwlock); + if ((value = switch_core_hash_find(globals.db_hash, hash_key))) { + stream->write_function(stream, "%s", value); + } + } else { + goto usage; + } + + goto done; + + usage: + stream->write_function(stream, "-ERR Usage: hash %s\n", HASH_API_USAGE); + + done: + switch_thread_rwlock_unlock(globals.db_hash_rwlock); + switch_safe_free(mydata); + switch_safe_free(hash_key); + + return SWITCH_STATUS_SUCCESS; +} + +/* INIT/DEINIT STUFF */ + +SWITCH_MODULE_LOAD_FUNCTION(mod_hash_load) +{ + switch_application_interface_t *app_interface; + switch_api_interface_t *commands_api_interface; + switch_limit_interface_t *limit_interface; + switch_status_t status; + + memset(&globals, 0, sizeof(&globals)); + globals.pool = pool; + + status = switch_event_reserve_subclass(LIMIT_EVENT_USAGE); + if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_INUSE) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't register event subclass \"%s\" (%d)\n", LIMIT_EVENT_USAGE, status); + return SWITCH_STATUS_FALSE; + } + + switch_thread_rwlock_create(&globals.limit_hash_rwlock, globals.pool); + switch_thread_rwlock_create(&globals.db_hash_rwlock, globals.pool); + switch_core_hash_init(&globals.limit_hash, pool); + switch_core_hash_init(&globals.db_hash, pool); + + /* connect my internal structure to the blank pointer passed to me */ + *module_interface = switch_loadable_module_create_module_interface(pool, modname); + + /* register limit interfaces */ + SWITCH_ADD_LIMIT(limit_interface, "hash", limit_incr_hash, limit_release_hash, limit_usage_hash, limit_reset_hash, limit_status_hash); + + switch_scheduler_add_task(switch_epoch_time_now(NULL) + LIMIT_HASH_CLEANUP_INTERVAL, limit_hash_cleanup_callback, "limit_hash_cleanup", "mod_hash", 0, NULL, + SSHF_NONE); + + SWITCH_ADD_APP(app_interface, "hash", "Insert into the hashtable", HASH_DESC, hash_function, HASH_USAGE, SAF_SUPPORT_NOMEDIA) + SWITCH_ADD_API(commands_api_interface, "hash", "hash get/set", hash_api_function, "[insert|delete|select]///"); + switch_console_set_complete("add hash insert"); + switch_console_set_complete("add hash delete"); + switch_console_set_complete("add hash select"); + + /* indicate that the module should continue to be loaded */ + return SWITCH_STATUS_SUCCESS; + +} + + +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_hash_shutdown) +{ + switch_scheduler_del_task_group("mod_hash"); + + switch_thread_rwlock_destroy(globals.db_hash_rwlock); + switch_thread_rwlock_destroy(globals.limit_hash_rwlock); + + switch_core_hash_destroy(&globals.limit_hash); + switch_core_hash_destroy(&globals.db_hash); + + 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: + */ diff --git a/src/mod/applications/mod_lcr/mod_lcr.c b/src/mod/applications/mod_lcr/mod_lcr.c old mode 100755 new mode 100644 index aa1642650c..07bb67cd49 --- a/src/mod/applications/mod_lcr/mod_lcr.c +++ b/src/mod/applications/mod_lcr/mod_lcr.c @@ -126,7 +126,6 @@ struct profile_obj { switch_bool_t reorder_by_rate; switch_bool_t quote_in_list; switch_bool_t info_in_headers; - switch_bool_t enable_sip_redir; }; typedef struct profile_obj profile_t; @@ -295,18 +294,9 @@ static char *get_bridge_data(switch_memory_pool_t *pool, char *dialed_number, ch user_rate = switch_core_sprintf(pool, ",lcr_user_rate=%s", cur_route->user_rate_str); } - if (profile->enable_sip_redir) { - data = - switch_core_sprintf(pool, "%s%s%s%s%s", cur_route->gw_prefix, cur_route->prefix, destination_number, cur_route->suffix, cur_route->gw_suffix); - } else { - data = - switch_core_sprintf(pool, "[lcr_carrier=%s,lcr_rate=%s%s%s%s%s]%s%s%s%s%s", cur_route->carrier_name, cur_route->rate_str, user_rate, codec, cid, - header, cur_route->gw_prefix, cur_route->prefix, destination_number, cur_route->suffix, cur_route->gw_suffix); - } - - if (session && (switch_string_var_check_const(data) || switch_string_has_escaped_data(data))) { - data = switch_channel_expand_variables(switch_core_session_get_channel(session), data); - } + data = + switch_core_sprintf(pool, "[lcr_carrier=%s,lcr_rate=%s%s%s%s%s]%s%s%s%s%s", cur_route->carrier_name, cur_route->rate_str, user_rate, codec, cid, + header, cur_route->gw_prefix, cur_route->prefix, destination_number, cur_route->suffix, cur_route->gw_suffix); switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Returning Dialstring %s\n", data); return data; @@ -415,7 +405,7 @@ static switch_bool_t db_check(char *sql) switch_cache_db_handle_t *dbh = NULL; if (globals.odbc_dsn && (dbh = lcr_get_db_handle())) { - if (switch_cache_db_execute_sql(dbh, sql, NULL) == SWITCH_STATUS_SUCCESS) { + if (switch_cache_db_execute_sql(dbh, sql, NULL) == SWITCH_ODBC_SUCCESS) { ret = SWITCH_TRUE; } } @@ -545,7 +535,8 @@ static switch_bool_t lcr_execute_sql_callback(char *sql, switch_core_db_callback switch_cache_db_handle_t *dbh = NULL; if (globals.odbc_dsn && (dbh = lcr_get_db_handle())) { - if (switch_cache_db_execute_sql_callback(dbh, sql, callback, pdata, NULL) != SWITCH_STATUS_SUCCESS) { + if (switch_cache_db_execute_sql_callback(dbh, sql, callback, pdata, NULL) + == SWITCH_ODBC_FAIL) { retval = SWITCH_FALSE; } else { retval = SWITCH_TRUE; @@ -912,7 +903,6 @@ static switch_status_t lcr_load_config() char *reorder_by_rate = NULL; char *quote_in_list = NULL; char *info_in_headers = NULL; - char *enable_sip_redir = NULL; char *id_s = NULL; char *custom_sql = NULL; int argc, x = 0; @@ -965,8 +955,6 @@ static switch_status_t lcr_load_config() info_in_headers = val; } else if (!strcasecmp(var, "quote_in_list") && !zstr(val)) { quote_in_list = val; - } else if (!strcasecmp(var, "enable_sip_redir") && !zstr(val)) { - enable_sip_redir = val; } } @@ -1057,10 +1045,6 @@ static switch_status_t lcr_load_config() profile->info_in_headers = switch_true(info_in_headers); } - if (!zstr(enable_sip_redir)) { - profile->enable_sip_redir = switch_true(enable_sip_redir); - } - if (!zstr(quote_in_list)) { profile->quote_in_list = switch_true(quote_in_list); } @@ -1268,11 +1252,7 @@ SWITCH_STANDARD_APP(lcr_app_function) switch_channel_set_variable(channel, vbuf, cur_route->codec); cnt++; if (cur_route->next) { - if (routes.profile->enable_sip_redir) { - dig_stream.write_function(&dig_stream, "%s,", cur_route->dialstring); - } else { - dig_stream.write_function(&dig_stream, "%s|", cur_route->dialstring); - } + dig_stream.write_function(&dig_stream, "%s|", cur_route->dialstring); } else { dig_stream.write_function(&dig_stream, "%s", cur_route->dialstring); } @@ -1519,7 +1499,6 @@ SWITCH_STANDARD_API(dialplan_lcr_admin_function) stream->write_function(stream, " has npanxx:\t%s\n", profile->profile_has_npanxx ? "true" : "false"); stream->write_function(stream, " Reorder rate:\t%s\n", profile->reorder_by_rate ? "enabled" : "disabled"); stream->write_function(stream, " Info in headers:\t%s\n", profile->info_in_headers ? "enabled" : "disabled"); - stream->write_function(stream, " Sip Redirection Mode:\t%s\n", profile->enable_sip_redir ? "enabled" : "disabled"); stream->write_function(stream, " Quote IN() List:\t%s\n", profile->quote_in_list ? "enabled" : "disabled"); stream->write_function(stream, "\n"); } diff --git a/src/mod/applications/mod_limit/mod_limit.c b/src/mod/applications/mod_limit/mod_limit.c deleted file mode 100644 index b32cb129a9..0000000000 --- a/src/mod/applications/mod_limit/mod_limit.c +++ /dev/null @@ -1,1350 +0,0 @@ -/* - * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - * Copyright (C) 2005-2010, 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 - * Ken Rice - * Bret McDanel - * Rupa Schomaker - * - * mod_limit.c -- Resource Limit Module - * - */ - -#include - -#define LIMIT_EVENT_USAGE "limit::usage" -#define LIMIT_IGNORE_TRANSFER_VARIABLE "limit_ignore_transfer" -#define LIMIT_HASH_CLEANUP_INTERVAL 900 - -SWITCH_MODULE_LOAD_FUNCTION(mod_limit_load); -SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_limit_shutdown); -SWITCH_MODULE_DEFINITION(mod_limit, mod_limit_load, mod_limit_shutdown, NULL); - -static struct { - switch_memory_pool_t *pool; - char hostname[256]; - char *dbname; - char *odbc_dsn; - char *odbc_user; - char *odbc_pass; - switch_mutex_t *mutex; - switch_mutex_t *limit_hash_mutex; - switch_hash_t *limit_hash; - switch_mutex_t *db_hash_mutex; - switch_hash_t *db_hash; -} globals; - -typedef struct { - uint32_t total_usage; - uint32_t rate_usage; - time_t last_check; - uint32_t interval; -} limit_hash_item_t; - -typedef struct { - switch_hash_t *hash; -} limit_hash_private_t; - -static char limit_sql[] = - "CREATE TABLE limit_data (\n" - " hostname VARCHAR(255),\n" " realm VARCHAR(255),\n" " id VARCHAR(255),\n" " uuid VARCHAR(255)\n" ");\n"; - -static char db_sql[] = - "CREATE TABLE db_data (\n" - " hostname VARCHAR(255),\n" " realm VARCHAR(255),\n" " data_key VARCHAR(255),\n" " data VARCHAR(255)\n" ");\n"; - -static char group_sql[] = - "CREATE TABLE group_data (\n" " hostname VARCHAR(255),\n" " groupname VARCHAR(255),\n" " url VARCHAR(255)\n" ");\n"; - - -switch_cache_db_handle_t *limit_get_db_handle(void) -{ - switch_cache_db_connection_options_t options = { {0} }; - switch_cache_db_handle_t *dbh = NULL; - - if (!zstr(globals.odbc_dsn)) { - options.odbc_options.dsn = globals.odbc_dsn; - options.odbc_options.user = globals.odbc_user; - options.odbc_options.pass = globals.odbc_pass; - - if (switch_cache_db_get_db_handle(&dbh, SCDB_TYPE_ODBC, &options) != SWITCH_STATUS_SUCCESS) - dbh = NULL; - return dbh; - } else { - options.core_db_options.db_path = globals.dbname; - if (switch_cache_db_get_db_handle(&dbh, SCDB_TYPE_CORE_DB, &options) != SWITCH_STATUS_SUCCESS) - dbh = NULL; - return dbh; - } -} - - -static switch_status_t limit_execute_sql(char *sql, switch_mutex_t *mutex) -{ - switch_cache_db_handle_t *dbh = NULL; - switch_status_t status = SWITCH_STATUS_FALSE; - - if (mutex) { - switch_mutex_lock(mutex); - } - - if (!(dbh = limit_get_db_handle())) { - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Opening DB\n"); - goto end; - } - - status = switch_cache_db_execute_sql(dbh, sql, NULL); - - end: - - switch_cache_db_release_db_handle(&dbh); - - if (mutex) { - switch_mutex_unlock(mutex); - } - - return status; -} - -static switch_bool_t limit_execute_sql_callback(switch_mutex_t *mutex, char *sql, switch_core_db_callback_func_t callback, void *pdata) -{ - switch_bool_t ret = SWITCH_FALSE; - char *errmsg = NULL; - switch_cache_db_handle_t *dbh = NULL; - - if (mutex) { - switch_mutex_lock(mutex); - } - - if (!(dbh = limit_get_db_handle())) { - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Opening DB\n"); - goto end; - } - - switch_cache_db_execute_sql_callback(dbh, sql, callback, pdata, &errmsg); - - if (errmsg) { - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SQL ERR: [%s] %s\n", sql, errmsg); - free(errmsg); - } - - end: - - switch_cache_db_release_db_handle(&dbh); - - if (mutex) { - switch_mutex_unlock(mutex); - } - - return ret; -} - - -static switch_xml_config_string_options_t limit_config_dsn = { NULL, 0, "[^:]+:[^:]+:.+" }; - -static switch_xml_config_item_t config_settings[] = { - SWITCH_CONFIG_ITEM("odbc-dsn", SWITCH_CONFIG_STRING, 0, &globals.odbc_dsn, NULL, &limit_config_dsn, - "dsn:username:password", "If set, the ODBC DSN used by the limit and db applications"), - SWITCH_CONFIG_ITEM_END() -}; - -static switch_status_t do_config() -{ - switch_cache_db_handle_t *dbh = NULL; - switch_status_t status = SWITCH_STATUS_SUCCESS; - char *sql = NULL; - - limit_config_dsn.pool = globals.pool; - - if (switch_xml_config_parse_module_settings("limit.conf", SWITCH_FALSE, config_settings) != SWITCH_STATUS_SUCCESS) { - return SWITCH_STATUS_TERM; - } - - if (globals.odbc_dsn) { - if ((globals.odbc_user = strchr(globals.odbc_dsn, ':'))) { - *globals.odbc_user++ = '\0'; - if ((globals.odbc_pass = strchr(globals.odbc_user, ':'))) { - *globals.odbc_pass++ = '\0'; - } - } - - if (!(dbh = limit_get_db_handle())) { - globals.odbc_dsn = globals.odbc_user = globals.odbc_pass; - } - } - - - if (zstr(globals.odbc_dsn)) { - globals.dbname = "call_limit"; - dbh = limit_get_db_handle(); - } - - - if (dbh) { - int x = 0; - char *indexes[] = { - "create index ld_hostname on limit_data (hostname)", - "create index ld_uuid on limit_data (uuid)", - "create index ld_realm on limit_data (realm)", - "create index ld_id on limit_data (id)", - "create index dd_realm on db_data (realm)", - "create index dd_data_key on db_data (data_key)", - "create index gd_groupname on group_data (groupname)", - "create index gd_url on group_data (url)", - NULL - }; - - - - switch_cache_db_test_reactive(dbh, "select * from limit_data", NULL, limit_sql); - switch_cache_db_test_reactive(dbh, "select * from db_data", NULL, db_sql); - switch_cache_db_test_reactive(dbh, "select * from group_data", NULL, group_sql); - - for (x = 0; indexes[x]; x++) { - switch_cache_db_execute_sql(dbh, indexes[x], NULL); - } - - switch_cache_db_release_db_handle(&dbh); - - sql = switch_mprintf("delete from limit_data where hostname='%q';", globals.hostname); - limit_execute_sql(sql, globals.mutex); - switch_safe_free(sql); - } - - return status; -} - -static void limit_fire_event(const char *realm, const char *key, uint32_t usage, uint32_t rate, uint32_t max, uint32_t ratemax) -{ - switch_event_t *event; - - if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, LIMIT_EVENT_USAGE) == SWITCH_STATUS_SUCCESS) { - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "realm", realm); - switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "key", key); - switch_event_add_header(event, SWITCH_STACK_BOTTOM, "usage", "%d", usage); - switch_event_add_header(event, SWITCH_STACK_BOTTOM, "rate", "%d", rate); - switch_event_add_header(event, SWITCH_STACK_BOTTOM, "max", "%d", max); - switch_event_add_header(event, SWITCH_STACK_BOTTOM, "ratemax", "%d", ratemax); - switch_event_fire(&event); - } -} - -static switch_status_t db_state_handler(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); - char *sql = NULL; - const char *vval = switch_channel_get_variable(channel, LIMIT_IGNORE_TRANSFER_VARIABLE); - - if (state >= CS_HANGUP || (state == CS_ROUTING && !switch_true(vval))) { - sql = switch_mprintf("delete from limit_data where uuid='%q';", switch_core_session_get_uuid(session)); - limit_execute_sql(sql, globals.mutex); - switch_safe_free(sql); - switch_core_event_hook_remove_state_change(session, db_state_handler); - /* Remove limit_realm variable so we register another hook if limit is called again */ - switch_channel_set_variable(channel, "limit_realm", NULL); - } - return SWITCH_STATUS_SUCCESS; -} - -static switch_status_t hash_state_handler(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); - limit_hash_private_t *pvt = switch_channel_get_private(channel, "limit_hash"); - const char *vval = switch_channel_get_variable(channel, LIMIT_IGNORE_TRANSFER_VARIABLE); - - /* The call is either hung up, or is going back into the dialplan, decrement appropriate couters */ - if (state >= CS_HANGUP || (state == CS_ROUTING && !switch_true(vval))) { - switch_hash_index_t *hi; - switch_mutex_lock(globals.limit_hash_mutex); - - /* Loop through the channel's hashtable which contains mapping to all the limit_hash_item_t referenced by that channel - while() idiom used -- 'cause pvt->hash is being completely removed - */ - while ((hi = switch_hash_first(NULL, pvt->hash))) { - void *val = NULL; - const void *key; - switch_ssize_t keylen; - limit_hash_item_t *item = NULL; - - switch_hash_this(hi, &key, &keylen, &val); - - item = (limit_hash_item_t *) val; - item->total_usage--; - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Usage for %s is now %d\n", (const char *) key, item->total_usage); - - if (item->total_usage == 0 && item->rate_usage == 0) { - /* Noone is using this item anymore */ - switch_core_hash_delete(globals.limit_hash, (const char *) key); - free(item); - } - - switch_core_hash_delete(pvt->hash, (const char *) key); - } - - /* Remove handler */ - switch_core_event_hook_remove_state_change(session, hash_state_handler); - - switch_mutex_unlock(globals.limit_hash_mutex); - } - - return SWITCH_STATUS_SUCCESS; -} - -struct callback { - char *buf; - size_t len; - int matches; -}; - -typedef struct callback callback_t; - -static int sql2str_callback(void *pArg, int argc, char **argv, char **columnNames) -{ - callback_t *cbt = (callback_t *) pArg; - - switch_copy_string(cbt->buf, argv[0], cbt->len); - cbt->matches++; - return 0; -} - -static int group_callback(void *pArg, int argc, char **argv, char **columnNames) -{ - callback_t *cbt = (callback_t *) pArg; - switch_snprintf(cbt->buf + strlen(cbt->buf), cbt->len - strlen(cbt->buf), "%s%c", argv[0], *argv[1]); - cbt->matches++; - return 0; -} - -SWITCH_STANDARD_API(db_api_function) -{ - int argc = 0; - char *argv[4] = { 0 }; - char *mydata = NULL; - char *sql; - - switch_mutex_lock(globals.mutex); - - if (!zstr(cmd)) { - mydata = strdup(cmd); - switch_assert(mydata); - argc = switch_separate_string(mydata, '/', argv, (sizeof(argv) / sizeof(argv[0]))); - } - - if (argc < 1 || !argv[0]) { - goto error; - } - - if (!strcasecmp(argv[0], "insert")) { - if (argc < 4) { - goto error; - } - sql = switch_mprintf("delete from db_data where realm='%q' and data_key='%q'", argv[1], argv[2]); - switch_assert(sql); - limit_execute_sql(sql, NULL); - switch_safe_free(sql); - sql = - switch_mprintf("insert into db_data (hostname, realm, data_key, data) values('%q','%q','%q','%q');", globals.hostname, argv[1], argv[2], - argv[3]); - switch_assert(sql); - limit_execute_sql(sql, NULL); - switch_safe_free(sql); - stream->write_function(stream, "+OK"); - goto done; - } else if (!strcasecmp(argv[0], "delete")) { - if (argc < 2) { - goto error; - } - sql = switch_mprintf("delete from db_data where realm='%q' and data_key='%q'", argv[1], argv[2]); - switch_assert(sql); - limit_execute_sql(sql, NULL); - switch_safe_free(sql); - stream->write_function(stream, "+OK"); - goto done; - } else if (!strcasecmp(argv[0], "select")) { - char buf[256] = ""; - callback_t cbt = { 0 }; - if (argc < 3) { - goto error; - } - cbt.buf = buf; - cbt.len = sizeof(buf); - sql = switch_mprintf("select data from db_data where realm='%q' and data_key='%q'", argv[1], argv[2]); - limit_execute_sql_callback(NULL, sql, sql2str_callback, &cbt); - switch_safe_free(sql); - stream->write_function(stream, "%s", buf); - goto done; - } - - error: - stream->write_function(stream, "!err!"); - - done: - - switch_mutex_unlock(globals.mutex); - switch_safe_free(mydata); - return SWITCH_STATUS_SUCCESS; -} - -#define DB_USAGE "[insert|delete]///" -#define DB_DESC "save data" - -SWITCH_STANDARD_APP(db_function) -{ - int argc = 0; - char *argv[4] = { 0 }; - char *mydata = NULL; - char *sql = NULL; - - if (!zstr(data)) { - mydata = switch_core_session_strdup(session, data); - argc = switch_separate_string(mydata, '/', argv, (sizeof(argv) / sizeof(argv[0]))); - } - - if (argc < 3 || !argv[0]) { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "USAGE: db %s\n", DB_USAGE); - return; - } - - if (!strcasecmp(argv[0], "insert")) { - if (argc < 4) { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "USAGE: db %s\n", DB_USAGE); - return; - } - sql = switch_mprintf("delete from db_data where realm='%q' and data_key='%q'", argv[1], argv[2]); - switch_assert(sql); - limit_execute_sql(sql, globals.mutex); - switch_safe_free(sql); - - sql = - switch_mprintf("insert into db_data (hostname, realm, data_key, data) values('%q','%q','%q','%q');", globals.hostname, argv[1], argv[2], - argv[3]); - } else if (!strcasecmp(argv[0], "delete")) { - sql = switch_mprintf("delete from db_data where realm='%q' and data_key='%q'", argv[1], argv[2]); - } else { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "USAGE: db %s\n", DB_USAGE); - return; - } - - if (sql) { - limit_execute_sql(sql, globals.mutex); - switch_safe_free(sql); - } -} - -#define HASH_USAGE "[insert|delete]///" -#define HASH_DESC "save data" - -SWITCH_STANDARD_APP(hash_function) -{ - int argc = 0; - char *argv[4] = { 0 }; - char *mydata = NULL; - char *hash_key = NULL; - char *value = NULL; - - switch_mutex_lock(globals.db_hash_mutex); - - if (!zstr(data)) { - mydata = strdup(data); - switch_assert(mydata); - argc = switch_separate_string(mydata, '/', argv, (sizeof(argv) / sizeof(argv[0]))); - } - - if (argc < 3 || !argv[0]) { - goto usage; - } - - hash_key = switch_mprintf("%s_%s", argv[1], argv[2]); - - if (!strcasecmp(argv[0], "insert")) { - if (argc < 4) { - goto usage; - } - if ((value = switch_core_hash_find(globals.db_hash, hash_key))) { - free(value); - switch_core_hash_delete(globals.db_hash, hash_key); - } - value = strdup(argv[3]); - switch_assert(value); - switch_core_hash_insert(globals.db_hash, hash_key, value); - } else if (!strcasecmp(argv[0], "delete")) { - if ((value = switch_core_hash_find(globals.db_hash, hash_key))) { - switch_safe_free(value); - switch_core_hash_delete(globals.db_hash, hash_key); - } - } else { - goto usage; - } - - goto done; - - usage: - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "USAGE: hash %s\n", HASH_USAGE); - - done: - switch_mutex_unlock(globals.db_hash_mutex); - switch_safe_free(mydata); - switch_safe_free(hash_key); -} - -#define HASH_API_USAGE "insert|select|delete/realm/key[/value]" -SWITCH_STANDARD_API(hash_api_function) -{ - int argc = 0; - char *argv[4] = { 0 }; - char *mydata = NULL; - char *value = NULL; - char *hash_key = NULL; - - switch_mutex_lock(globals.db_hash_mutex); - - if (!zstr(cmd)) { - mydata = strdup(cmd); - switch_assert(mydata); - argc = switch_separate_string(mydata, '/', argv, (sizeof(argv) / sizeof(argv[0]))); - } - - if (argc < 3 || !argv[0]) { - goto usage; - } - - hash_key = switch_mprintf("%s_%s", argv[1], argv[2]); - - if (!strcasecmp(argv[0], "insert")) { - if (argc < 4) { - goto usage; - } - if ((value = switch_core_hash_find(globals.db_hash, hash_key))) { - switch_safe_free(value); - switch_core_hash_delete(globals.db_hash, hash_key); - } - value = strdup(argv[3]); - switch_assert(value); - switch_core_hash_insert(globals.db_hash, hash_key, value); - stream->write_function(stream, "+OK\n"); - } else if (!strcasecmp(argv[0], "delete")) { - if ((value = switch_core_hash_find(globals.db_hash, hash_key))) { - switch_safe_free(value); - switch_core_hash_delete(globals.db_hash, hash_key); - stream->write_function(stream, "+OK\n"); - } else { - stream->write_function(stream, "-ERR Not found\n"); - } - } else if (!strcasecmp(argv[0], "select")) { - if ((value = switch_core_hash_find(globals.db_hash, hash_key))) { - stream->write_function(stream, "%s", value); - } - } else { - goto usage; - } - - goto done; - - usage: - stream->write_function(stream, "-ERR Usage: hash %s\n", HASH_API_USAGE); - - done: - switch_mutex_unlock(globals.db_hash_mutex); - switch_safe_free(mydata); - switch_safe_free(hash_key); - - return SWITCH_STATUS_SUCCESS; -} - -SWITCH_STANDARD_API(group_api_function) -{ - int argc = 0; - char *argv[4] = { 0 }; - char *mydata = NULL; - char *sql; - - switch_mutex_lock(globals.mutex); - - if (!zstr(cmd)) { - mydata = strdup(cmd); - argc = switch_separate_string(mydata, ':', argv, (sizeof(argv) / sizeof(argv[0]))); - } - - if (argc < 2 || !argv[0]) { - goto error; - } - - if (!strcasecmp(argv[0], "insert")) { - if (argc < 3) { - goto error; - } - sql = switch_mprintf("delete from group_data where groupname='%q' and url='%q';", argv[1], argv[2]); - switch_assert(sql); - - limit_execute_sql(sql, NULL); - switch_safe_free(sql); - sql = switch_mprintf("insert into group_data (hostname, groupname, url) values('%q','%q','%q');", globals.hostname, argv[1], argv[2]); - switch_assert(sql); - limit_execute_sql(sql, NULL); - switch_safe_free(sql); - stream->write_function(stream, "+OK"); - goto done; - } else if (!strcasecmp(argv[0], "delete")) { - if (argc < 3) { - goto error; - } - if (!strcmp(argv[2], "*")) { - sql = switch_mprintf("delete from group_data where groupname='%q';", argv[1]); - } else { - sql = switch_mprintf("delete from group_data where groupname='%q' and url='%q';", argv[1], argv[2]); - } - switch_assert(sql); - limit_execute_sql(sql, NULL); - switch_safe_free(sql); - stream->write_function(stream, "+OK"); - goto done; - } else if (!strcasecmp(argv[0], "call")) { - char buf[4096] = ""; - char *how = ","; - callback_t cbt = { 0 }; - cbt.buf = buf; - cbt.len = sizeof(buf); - - if (argc > 2) { - if (!strcasecmp(argv[2], "order")) { - how = "|"; - } - } - - sql = switch_mprintf("select url,'%q' from group_data where groupname='%q'", how, argv[1]); - switch_assert(sql); - - limit_execute_sql_callback(NULL, sql, group_callback, &cbt); - switch_safe_free(sql); - - if (!zstr(buf)) { - *(buf + (strlen(buf) - 1)) = '\0'; - } - - stream->write_function(stream, "%s", buf); - - goto done; - } - - error: - stream->write_function(stream, "!err!"); - - done: - - switch_mutex_unlock(globals.mutex); - switch_safe_free(mydata); - return SWITCH_STATUS_SUCCESS; -} - -#define GROUP_USAGE "[insert|delete]::" -#define GROUP_DESC "save data" - -SWITCH_STANDARD_APP(group_function) -{ - int argc = 0; - char *argv[3] = { 0 }; - char *mydata = NULL; - char *sql; - - if (!zstr(data)) { - mydata = switch_core_session_strdup(session, data); - argc = switch_separate_string(mydata, ':', argv, (sizeof(argv) / sizeof(argv[0]))); - } - - if (argc < 3 || !argv[0]) { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "USAGE: group %s\n", DB_USAGE); - return; - } - - if (!strcasecmp(argv[0], "insert")) { - sql = switch_mprintf("insert into group_data (hostname, groupname, url) values('%q','%q','%q');", globals.hostname, argv[1], argv[2]); - switch_assert(sql); - limit_execute_sql(sql, globals.mutex); - switch_safe_free(sql); - } else if (!strcasecmp(argv[0], "delete")) { - sql = switch_mprintf("delete from group_data where groupname='%q' and url='%q';", argv[1], argv[2]); - switch_assert(sql); - limit_execute_sql(sql, globals.mutex); - switch_safe_free(sql); - } -} - -/* \brief Enforces limit restrictions - * \param session current session - * \param realm limit realm - * \param id limit id - * \param max maximum count - * \return SWITCH_TRUE if the access is allowed, SWITCH_FALSE if it isnt - */ -static switch_bool_t do_limit(switch_core_session_t *session, const char *realm, const char *id, int max) -{ - switch_channel_t *channel = switch_core_session_get_channel(session); - int got = 0; - char *sql = NULL; - char buf[80] = ""; - callback_t cbt = { 0 }; - switch_status_t status = SWITCH_TRUE; - - switch_mutex_lock(globals.mutex); - - switch_channel_set_variable(channel, "limit_realm", realm); - switch_channel_set_variable(channel, "limit_id", id); - switch_channel_set_variable(channel, "limit_max", switch_core_session_sprintf(session, "%d", max)); - - cbt.buf = buf; - cbt.len = sizeof(buf); - sql = switch_mprintf("select count(hostname) from limit_data where realm='%q' and id='%q'", realm, id); - limit_execute_sql_callback(NULL, sql, sql2str_callback, &cbt); - switch_safe_free(sql); - got = atoi(buf); - - if (max < 0) { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Usage for %s_%s is now %d\n", realm, id, got + 1); - } else { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Usage for %s_%s is now %d/%d\n", realm, id, got + 1, max); - } - - if (max >= 0 && got + 1 > max) { - status = SWITCH_FALSE; - goto done; - } - - switch_core_event_hook_add_state_change(session, db_state_handler); - - sql = - switch_mprintf("insert into limit_data (hostname, realm, id, uuid) values('%q','%q','%q','%q');", globals.hostname, realm, id, - switch_core_session_get_uuid(session)); - limit_execute_sql(sql, NULL); - switch_safe_free(sql); - - { - const char *susage = switch_core_session_sprintf(session, "%d", ++got); - - switch_channel_set_variable(channel, "limit_usage", susage); - switch_channel_set_variable(channel, switch_core_session_sprintf(session, "limit_usage_%s_%s", realm, id), susage); - } - limit_fire_event(realm, id, got, 0, max, 0); - - done: - switch_mutex_unlock(globals.mutex); - return status; -} - -#define LIMIT_USAGE " [ [number [dialplan [context]]]]" -#define LIMIT_DESC "limit access to a resource and transfer to an extension if the limit is exceeded" -static char *limit_def_xfer_exten = "limit_exceeded"; - -SWITCH_STANDARD_APP(limit_function) -{ - int argc = 0; - char *argv[6] = { 0 }; - char *mydata = NULL; - char *realm = NULL; - char *id = NULL; - char *xfer_exten = NULL; - int max = 0; - switch_channel_t *channel = switch_core_session_get_channel(session); - - if (!zstr(data)) { - mydata = switch_core_session_strdup(session, data); - argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); - } - - if (argc < 3) { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "USAGE: limit %s\n", LIMIT_USAGE); - return; - } - - realm = argv[0]; - id = argv[1]; - - /* Accept '-' as unlimited (act as counter) */ - if (argv[2][0] == '-') { - max = -1; - } else { - max = atoi(argv[2]); - - if (max < 0) { - max = 0; - } - } - - if (argc >= 4) { - xfer_exten = argv[3]; - } else { - xfer_exten = limit_def_xfer_exten; - } - - if (!do_limit(session, realm, id, max)) { - /* Limit exceeded */ - if (*xfer_exten == '!') { - switch_channel_hangup(channel, switch_channel_str2cause(xfer_exten + 1)); - } else { - switch_ivr_session_transfer(session, xfer_exten, argv[4], argv[5]); - } - } - -} - -/* !\brief Releases usage of a limit_-controlled ressource */ -static void limit_release(switch_core_session_t *session, const char *realm, const char *id) -{ - char *sql = NULL; - - sql = switch_mprintf("delete from limit_data where uuid='%q' and realm='%q' and id like '%q'", switch_core_session_get_uuid(session), realm, id); - limit_execute_sql(sql, globals.mutex); - switch_safe_free(sql); -} - -#define LIMITEXECUTE_USAGE " [] [application] [application arguments]" -#define LIMITEXECUTE_DESC "limit access to a resource. the specified application will only be executed if the resource is available" -SWITCH_STANDARD_APP(limit_execute_function) -{ - int argc = 0; - char *argv[5] = { 0 }; - char *mydata = NULL; - char *realm = NULL; - char *id = NULL; - char *app = NULL; - char *app_arg = NULL; - int max = -1; - - /* Parse application data */ - if (!zstr(data)) { - mydata = switch_core_session_strdup(session, data); - argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); - } - - if (argc < 2) { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "USAGE: limit_execute %s\n", LIMITEXECUTE_USAGE); - return; - } - - realm = argv[0]; - id = argv[1]; - - /* Accept '-' as unlimited (act as counter) */ - if (argv[2][0] == '-') { - max = -1; - } else { - max = atoi(argv[2]); - - if (max < 0) { - max = 0; - } - } - - app = argv[3]; - app_arg = argv[4]; - - if (zstr(app)) { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Missing application\n"); - return; - } - - if (do_limit(session, realm, id, max)) { - switch_core_session_execute_application(session, app, app_arg); - limit_release(session, realm, id); - } -} - -#define LIMIT_USAGE_USAGE " " -SWITCH_STANDARD_API(limit_usage_function) -{ - int argc = 0; - char *argv[6] = { 0 }; - char *mydata = NULL; - char *sql = NULL; - char *realm = NULL; - char *id = NULL; - char buf[80] = ""; - callback_t cbt = { 0 }; - - - if (!zstr(cmd)) { - mydata = strdup(cmd); - switch_assert(mydata); - argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); - } - - if (argc < 2) { - stream->write_function(stream, "USAGE: limit_usage %s\n", LIMIT_USAGE_USAGE); - goto end; - } - - - realm = argv[0]; - id = argv[1]; - - cbt.buf = buf; - cbt.len = sizeof(buf); - sql = switch_mprintf("select count(hostname) from limit_data where realm='%q' and id like '%q'", realm, id); - limit_execute_sql_callback(NULL, sql, sql2str_callback, &cbt); - switch_safe_free(sql); - - stream->write_function(stream, "%s", buf); - - end: - switch_safe_free(mydata); - return SWITCH_STATUS_SUCCESS; -} - -/* \brief Enforces limit_hash restrictions - * \param session current session - * \param realm limit realm - * \param id limit id - * \param max maximum count - * \param interval interval for rate limiting - * \return SWITCH_TRUE if the access is allowed, SWITCH_FALSE if it isnt - */ -static switch_bool_t do_limit_hash(switch_core_session_t *session, const char *realm, const char *id, int max, int interval) -{ - switch_channel_t *channel = switch_core_session_get_channel(session); - char *hashkey = NULL; - switch_bool_t status = SWITCH_TRUE; - limit_hash_item_t *item = NULL; - time_t now = switch_epoch_time_now(NULL); - limit_hash_private_t *pvt = NULL; - uint8_t increment = 1; - - hashkey = switch_core_session_sprintf(session, "%s_%s", realm, id); - - switch_mutex_lock(globals.limit_hash_mutex); - /* Check if that realm+id has ever been checked */ - if (!(item = (limit_hash_item_t *) switch_core_hash_find(globals.limit_hash, hashkey))) { - /* No, create an empty structure and add it, then continue like as if it existed */ - item = (limit_hash_item_t *) malloc(sizeof(limit_hash_item_t)); - switch_assert(item); - memset(item, 0, sizeof(limit_hash_item_t)); - switch_core_hash_insert(globals.limit_hash, hashkey, item); - } - - /* Did we already run on this channel before? */ - if ((pvt = switch_channel_get_private(channel, "limit_hash"))) { - /* Yes, but check if we did that realm+id - If we didnt, allow incrementing the counter. - If we did, dont touch it but do the validation anyways - */ - increment = !switch_core_hash_find(pvt->hash, hashkey); - } else { - /* This is the first limit check on this channel, create a hashtable, set our prviate data and add a state handler */ - pvt = (limit_hash_private_t *) switch_core_session_alloc(session, sizeof(limit_hash_private_t)); - memset(pvt, 0, sizeof(limit_hash_private_t)); - switch_core_hash_init(&pvt->hash, switch_core_session_get_pool(session)); - switch_channel_set_private(channel, "limit_hash", pvt); - } - - if (interval > 0) { - /* interval is always the last used interval setting? */ - item->interval = interval; - if (item->last_check <= (now - interval)) { - item->rate_usage = 1; - item->last_check = now; - } else { - /* Always increment rate when its checked as it doesnt depend on the channel */ - item->rate_usage++; - - if ((max >= 0) && (item->rate_usage > (uint32_t) max)) { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Usage for %s exceeds maximum rate of %d/%ds, now at %d\n", - hashkey, max, interval, item->rate_usage); - status = SWITCH_FALSE; - goto end; - } - } - } else if ((max >= 0) && (item->total_usage + increment > (uint32_t) max)) { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Usage for %s is already at max value (%d)\n", hashkey, item->total_usage); - status = SWITCH_FALSE; - goto end; - } - - if (increment) { - item->total_usage++; - - switch_core_hash_insert(pvt->hash, hashkey, item); - - if (max == -1) { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Usage for %s is now %d\n", hashkey, item->total_usage); - } else if (interval == 0) { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Usage for %s is now %d/%d\n", hashkey, item->total_usage, max); - } else { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Usage for %s is now %d/%d for the last %d seconds\n", hashkey, - item->rate_usage, max, interval); - } - - limit_fire_event(realm, id, item->total_usage, item->rate_usage, max, max >= 0 ? (uint32_t) max : 0); - } - - /* Save current usage & rate into channel variables so it can be used later in the dialplan, or added to CDR records */ - { - const char *susage = switch_core_session_sprintf(session, "%d", item->total_usage); - const char *srate = switch_core_session_sprintf(session, "%d", item->rate_usage); - - switch_channel_set_variable(channel, "limit_usage", susage); - switch_channel_set_variable(channel, switch_core_session_sprintf(session, "limit_usage_%s", hashkey), susage); - - switch_channel_set_variable(channel, "limit_rate", srate); - switch_channel_set_variable(channel, switch_core_session_sprintf(session, "limit_rate_%s", hashkey), srate); - } - - switch_core_event_hook_add_state_change(session, hash_state_handler); - - end: - switch_mutex_unlock(globals.limit_hash_mutex); - return status; -} - -SWITCH_HASH_DELETE_FUNC(limit_hash_cleanup_delete_callback) { - limit_hash_item_t *item = (limit_hash_item_t *) val; - time_t now = switch_epoch_time_now(NULL); - - /* reset to 0 if window has passed so we can clean it up */ - if (item->rate_usage > 0 && (item->last_check <= (now - item->interval))) { - item->rate_usage = 0; - } - - if (item->total_usage == 0 && item->rate_usage == 0) { - /* Noone is using this item anymore */ - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Freeing limit item: %s\n", (const char *) key); - - free(item); - return SWITCH_TRUE; - } - - return SWITCH_FALSE; -} - -/* !\brief Periodically checks for unused limit entries and frees them */ -SWITCH_STANDARD_SCHED_FUNC(limit_hash_cleanup_callback) -{ - switch_mutex_lock(globals.limit_hash_mutex); - switch_core_hash_delete_multi(globals.limit_hash, limit_hash_cleanup_delete_callback, NULL); - switch_mutex_unlock(globals.limit_hash_mutex); - - task->runtime = switch_epoch_time_now(NULL) + LIMIT_HASH_CLEANUP_INTERVAL; -} - -/* !\brief Releases usage of a limit_hash-controlled ressource */ -static void limit_hash_release(switch_core_session_t *session, const char *realm, const char *id) -{ - switch_channel_t *channel = switch_core_session_get_channel(session); - limit_hash_private_t *pvt = switch_channel_get_private(channel, "limit_hash"); - limit_hash_item_t *item = NULL; - char *hashkey = NULL; - - if (!pvt || !pvt->hash) { - return; - } - - hashkey = switch_core_session_sprintf(session, "%s_%s", realm, id); - - switch_mutex_lock(globals.limit_hash_mutex); - - if ((item = (limit_hash_item_t *) switch_core_hash_find(pvt->hash, hashkey))) { - item->total_usage--; - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Usage for %s is now %d\n", (const char *) hashkey, item->total_usage); - - switch_core_hash_delete(pvt->hash, hashkey); - - if (item->total_usage == 0 && item->rate_usage == 0) { - /* Noone is using this item anymore */ - switch_core_hash_delete(globals.limit_hash, (const char *) hashkey); - free(item); - } - } - - switch_mutex_unlock(globals.limit_hash_mutex); -} - -#define LIMITHASH_USAGE " [[/interval]] [number [dialplan [context]]]" -#define LIMITHASH_DESC "limit access to a resource and transfer to an extension if the limit is exceeded" -SWITCH_STANDARD_APP(limit_hash_function) -{ - int argc = 0; - char *argv[6] = { 0 }; - char *mydata = NULL; - char *realm = NULL; - char *id = NULL; - char *xfer_exten = NULL; - int max = -1; - int interval = 0; - switch_channel_t *channel = switch_core_session_get_channel(session); - - /* Parse application data */ - if (!zstr(data)) { - mydata = switch_core_session_strdup(session, data); - argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); - } - - if (argc < 2) { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "USAGE: limit_hash %s\n", LIMITHASH_USAGE); - return; - } - - realm = argv[0]; - id = argv[1]; - - /* If max is omitted or negative, only act as a counter and skip maximum checks */ - if (argc > 2) { - if (argv[2][0] == '-') { - max = -1; - } else { - char *szinterval = NULL; - if ((szinterval = strchr(argv[2], '/'))) { - *szinterval++ = '\0'; - interval = atoi(szinterval); - } - - max = atoi(argv[2]); - - if (max < 0) { - max = 0; - } - } - } - - if (argc > 3) { - xfer_exten = argv[3]; - } else { - xfer_exten = limit_def_xfer_exten; - } - - if (!do_limit_hash(session, realm, id, max, interval)) { - /* Limit exceeded */ - if (*xfer_exten == '!') { - switch_channel_hangup(channel, switch_channel_str2cause(xfer_exten + 1)); - } else { - switch_ivr_session_transfer(session, xfer_exten, argv[4], argv[5]); - } - } -} - - -#define LIMITHASHEXECUTE_USAGE " [[/interval]] [application] [application arguments]" -#define LIMITHASHEXECUTE_DESC "limit access to a resource. the specified application will only be executed if the resource is available" -SWITCH_STANDARD_APP(limit_hash_execute_function) -{ - int argc = 0; - char *argv[5] = { 0 }; - char *mydata = NULL; - char *realm = NULL; - char *id = NULL; - char *app = NULL; - char *app_arg = NULL; - int max = -1; - int interval = 0; - - /* Parse application data */ - if (!zstr(data)) { - mydata = switch_core_session_strdup(session, data); - argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); - } - - if (argc < 5) { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "USAGE: limit_hash_execute %s\n", LIMITHASHEXECUTE_USAGE); - return; - } - - realm = argv[0]; - id = argv[1]; - - /* Accept '-' as unlimited (act as counter) */ - if (argv[2][0] == '-') { - max = -1; - } else { - char *szinterval = NULL; - - if ((szinterval = strchr(argv[2], '/'))) { - *szinterval++ = '\0'; - interval = atoi(szinterval); - } - - max = atoi(argv[2]); - - if (max < 0) { - max = 0; - } - } - - app = argv[3]; - app_arg = argv[4]; - - if (zstr(app)) { - switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Missing application\n"); - return; - } - - if (do_limit_hash(session, realm, id, max, interval)) { - switch_core_session_execute_application(session, app, app_arg); - /* Only release the resource if we are still in CS_EXECUTE */ - if (switch_channel_get_state(switch_core_session_get_channel(session)) == CS_EXECUTE) { - limit_hash_release(session, realm, id); - } - } -} - - -#define LIMIT_HASH_USAGE_USAGE " [rate]" -SWITCH_STANDARD_API(limit_hash_usage_function) -{ - int argc = 0; - char *argv[4] = { 0 }; - char *mydata = NULL; - char *hash_key = NULL; - limit_hash_item_t *item = NULL; - uint32_t count = 0, rcount = 0; - switch_bool_t dorate = SWITCH_FALSE; - - switch_mutex_lock(globals.limit_hash_mutex); - - if (!zstr(cmd)) { - mydata = strdup(cmd); - switch_assert(mydata); - argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); - } - - if (argc < 2) { - stream->write_function(stream, "USAGE: limit_hash_usage %s\n", LIMIT_HASH_USAGE_USAGE); - goto end; - } - - if (argc > 2) { - if (!strcasecmp(argv[2], "rate")) { - dorate = SWITCH_TRUE; - } - } - - hash_key = switch_mprintf("%s_%s", argv[0], argv[1]); - - if ((item = switch_core_hash_find(globals.limit_hash, hash_key))) { - count = item->total_usage; - rcount = item->rate_usage; - } - - if (dorate == SWITCH_TRUE) { - stream->write_function(stream, "%d/%d", count, rcount); - } else { - stream->write_function(stream, "%d", count); - } - - end: - switch_safe_free(mydata); - switch_safe_free(hash_key); - switch_mutex_unlock(globals.limit_hash_mutex); - - return SWITCH_STATUS_SUCCESS; -} - -SWITCH_MODULE_LOAD_FUNCTION(mod_limit_load) -{ - switch_status_t status; - switch_application_interface_t *app_interface; - switch_api_interface_t *commands_api_interface; - - memset(&globals, 0, sizeof(&globals)); - gethostname(globals.hostname, sizeof(globals.hostname)); - globals.pool = pool; - - - if (switch_event_reserve_subclass(LIMIT_EVENT_USAGE) != SWITCH_STATUS_SUCCESS) { - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldnt register event subclass \"%s\"", LIMIT_EVENT_USAGE); - return SWITCH_STATUS_FALSE; - } - - if ((status = do_config() != SWITCH_STATUS_SUCCESS)) { - return status; - } - - switch_mutex_init(&globals.mutex, SWITCH_MUTEX_NESTED, globals.pool); - switch_mutex_init(&globals.limit_hash_mutex, SWITCH_MUTEX_NESTED, globals.pool); - switch_mutex_init(&globals.db_hash_mutex, SWITCH_MUTEX_NESTED, globals.pool); - switch_core_hash_init(&globals.limit_hash, pool); - switch_core_hash_init(&globals.db_hash, pool); - - /* connect my internal structure to the blank pointer passed to me */ - *module_interface = switch_loadable_module_create_module_interface(pool, modname); - - switch_scheduler_add_task(switch_epoch_time_now(NULL) + LIMIT_HASH_CLEANUP_INTERVAL, limit_hash_cleanup_callback, "limit_hash_cleanup", "mod_limit", 0, NULL, - SSHF_NONE); - - SWITCH_ADD_APP(app_interface, "limit", "Limit", LIMIT_DESC, limit_function, LIMIT_USAGE, SAF_SUPPORT_NOMEDIA); - SWITCH_ADD_APP(app_interface, "limit_execute", "Limit", LIMITEXECUTE_USAGE, limit_execute_function, LIMITEXECUTE_USAGE, SAF_SUPPORT_NOMEDIA); - SWITCH_ADD_APP(app_interface, "limit_hash", "Limit (hash)", LIMITHASH_DESC, limit_hash_function, LIMITHASH_USAGE, SAF_SUPPORT_NOMEDIA); - SWITCH_ADD_APP(app_interface, "limit_hash_execute", "Limit (hash)", LIMITHASHEXECUTE_USAGE, limit_hash_execute_function, LIMITHASHEXECUTE_USAGE, - SAF_SUPPORT_NOMEDIA); - SWITCH_ADD_APP(app_interface, "db", "Insert to the db", DB_DESC, db_function, DB_USAGE, SAF_SUPPORT_NOMEDIA); - SWITCH_ADD_APP(app_interface, "hash", "Insert into the hashtable", HASH_DESC, hash_function, HASH_USAGE, SAF_SUPPORT_NOMEDIA) - SWITCH_ADD_APP(app_interface, "group", "Manage a group", GROUP_DESC, group_function, GROUP_USAGE, SAF_SUPPORT_NOMEDIA); - - SWITCH_ADD_API(commands_api_interface, "limit_hash_usage", "Gets the usage count of a limited resource", limit_hash_usage_function, - LIMIT_HASH_USAGE_USAGE); - SWITCH_ADD_API(commands_api_interface, "limit_usage", "Gets the usage count of a limited resource", limit_usage_function, " "); - SWITCH_ADD_API(commands_api_interface, "db", "db get/set", db_api_function, "[insert|delete|select]///"); - switch_console_set_complete("add db insert"); - switch_console_set_complete("add db delete"); - switch_console_set_complete("add db select"); - SWITCH_ADD_API(commands_api_interface, "hash", "hash get/set", hash_api_function, "[insert|delete|select]///"); - switch_console_set_complete("add hash insert"); - switch_console_set_complete("add hash delete"); - switch_console_set_complete("add hash select"); - SWITCH_ADD_API(commands_api_interface, "group", "group [insert|delete|call]", group_api_function, "[insert|delete|call]::"); - switch_console_set_complete("add group insert"); - switch_console_set_complete("add group delete"); - switch_console_set_complete("add group call"); - - /* indicate that the module should continue to be loaded */ - return SWITCH_STATUS_SUCCESS; -} - - -SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_limit_shutdown) -{ - switch_scheduler_del_task_group("mod_limit"); - switch_event_free_subclass(LIMIT_EVENT_USAGE); - - switch_xml_config_cleanup(config_settings); - - switch_mutex_destroy(globals.mutex); - switch_mutex_destroy(globals.limit_hash_mutex); - switch_mutex_destroy(globals.db_hash_mutex); - - switch_core_hash_destroy(&globals.limit_hash); - switch_core_hash_destroy(&globals.db_hash); - - 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: - */ diff --git a/src/mod/applications/mod_limit/mod_limit.vcproj b/src/mod/applications/mod_limit/mod_limit.vcproj deleted file mode 100644 index a15f5aa938..0000000000 --- a/src/mod/applications/mod_limit/mod_limit.vcproj +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/switch_ivr_originate.c b/src/switch_ivr_originate.c index 11e5bc5d01..c89013eb84 100644 --- a/src/switch_ivr_originate.c +++ b/src/switch_ivr_originate.c @@ -1392,6 +1392,8 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_enterprise_originate(switch_core_sess } } + switch_event_add_header_string(var_event, SWITCH_STACK_BOTTOM, "ent_originate_aleg_uuid", switch_core_session_get_uuid(session)); + /* A comma (,) separated list of variable names that should ne propagated from originator to originatee */ if (channel && (export_vars = switch_channel_get_variable(channel, SWITCH_EXPORT_VARS_VARIABLE))) { char *cptmp = switch_core_session_strdup(session, export_vars); diff --git a/src/switch_limit.c b/src/switch_limit.c new file mode 100755 index 0000000000..55648ee72b --- /dev/null +++ b/src/switch_limit.c @@ -0,0 +1,222 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2009, 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): + * + * Rupa Schomaker + * + * switch_limit.c Limit support + * + */ + +#include + +static switch_limit_interface_t *get_backend(const char *backend) { + switch_limit_interface_t *limit = NULL; + + if (!backend) { + return NULL; + } + + if (!(limit = switch_loadable_module_get_limit_interface(backend))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Unable to locate limit backend: %s\n", backend); + } + + return limit; +} + +static void release_backend(switch_limit_interface_t *limit) { + if (limit) { + UNPROTECT_INTERFACE(limit); + } +} + +SWITCH_DECLARE(void) switch_limit_init(switch_memory_pool_t *pool) { + if (switch_event_reserve_subclass(LIMIT_EVENT_USAGE) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldnt register event subclass \"%s\"", LIMIT_EVENT_USAGE); + } +} + + +SWITCH_DECLARE(void) switch_limit_fire_event(const char *backend, const char *realm, const char *key, uint32_t usage, uint32_t rate, uint32_t max, uint32_t ratemax) +{ + switch_event_t *event; + + if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, LIMIT_EVENT_USAGE) == SWITCH_STATUS_SUCCESS) { + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "backend", backend); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "realm", realm); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "key", key); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "usage", "%d", usage); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "rate", "%d", rate); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "max", "%d", max); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "ratemax", "%d", ratemax); + switch_event_fire(&event); + } +} + +static switch_status_t limit_state_handler(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); + const char *vval = switch_channel_get_variable(channel, LIMIT_IGNORE_TRANSFER_VARIABLE); + const char *backendlist = switch_channel_get_variable(channel, LIMIT_BACKEND_VARIABLE); + int argc = 0; + char *argv[6] = { 0 }; + char *mydata = NULL; + + if (zstr(backendlist)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unset limit backendlist!\n"); + return SWITCH_STATUS_SUCCESS; + } + + mydata = strdup(backendlist); + + if (state >= CS_HANGUP || (state == CS_ROUTING && !switch_true(vval))) { + int x; + argc = switch_separate_string(mydata, ',', argv, (sizeof(argv) / sizeof(argv[0]))); + for (x = 0; x < argc; x++) { + switch_limit_release(argv[x], session, NULL, NULL); + } + switch_core_event_hook_remove_state_change(session, limit_state_handler); + /* Remove limit_realm variable so we register another hook if limit is called again */ + switch_channel_set_variable(channel, "limit_realm", NULL); + } + return SWITCH_STATUS_SUCCESS; +} + + +SWITCH_DECLARE(switch_status_t) switch_limit_incr(const char *backend, switch_core_session_t *session, const char *realm, const char *resource, const int max, const int interval) { + switch_limit_interface_t *limit = NULL; + switch_channel_t *channel = NULL; + int status = SWITCH_STATUS_SUCCESS; + + assert(session); + + channel = switch_core_session_get_channel(session); + + /* locate impl, call appropriate func */ + if (!(limit = get_backend(backend))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Limit subsystem %s not found!\n", backend); + switch_goto_status(SWITCH_STATUS_GENERR, end); + } + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "incr called: %s_%s max:%d, interval:%d\n", + realm, resource, max, interval); + + if ((status = limit->incr(session, realm, resource, max, interval)) == SWITCH_STATUS_SUCCESS) { + /* race condition? what if another leg is doing the same thing? */ + const char *existing = switch_channel_get_variable(channel, LIMIT_BACKEND_VARIABLE); + if (existing) { + if (!strstr(existing, backend)) { + switch_channel_set_variable_printf(channel, LIMIT_BACKEND_VARIABLE, "%s,%s", existing, backend); + } + } else { + switch_channel_set_variable(channel, LIMIT_BACKEND_VARIABLE, backend); + switch_core_event_hook_add_state_change(session, limit_state_handler); + } + } + + release_backend(limit); + +end: + return status; +} + +SWITCH_DECLARE(switch_status_t) switch_limit_release(const char *backend, switch_core_session_t *session, const char *realm, const char *resource) { + switch_limit_interface_t *limit = NULL; + int status = SWITCH_STATUS_SUCCESS; + + /* locate impl, call appropriate func */ + if (!(limit = get_backend(backend))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Limit subsystem %s not found!\n", backend); + switch_goto_status(SWITCH_STATUS_GENERR, end); + } + + status = limit->release(session, realm, resource); + +end: + release_backend(limit); + return status; +} + +SWITCH_DECLARE(int) switch_limit_usage(const char *backend, const char *realm, const char *resource, uint32_t *rcount) { + switch_limit_interface_t *limit = NULL; + int usage = 0; + + /* locate impl, call appropriate func */ + if (!(limit = get_backend(backend))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Limit subsystem %s not found!\n", backend); + goto end; + } + + usage = limit->usage(realm, resource, rcount); + +end: + release_backend(limit); + return usage; +} + +SWITCH_DECLARE(switch_status_t) switch_limit_reset(const char *backend) { + switch_limit_interface_t *limit = NULL; + int status = SWITCH_STATUS_SUCCESS; + + /* locate impl, call appropriate func */ + if (!(limit = get_backend(backend))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Limit subsystem %s not found!\n", backend); + switch_goto_status(SWITCH_STATUS_GENERR, end); + } + + status = limit->reset(); + +end: + release_backend(limit); + return status; +} + +SWITCH_DECLARE(char *) switch_limit_status(const char *backend) { + switch_limit_interface_t *limit = NULL; + char *status = NULL; + + /* locate impl, call appropriate func */ + if (!(limit = get_backend(backend))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Limit subsystem %s not found!\n", backend); + switch_goto_status("-ERR", end); + } + + status = limit->status(); + +end: + release_backend(limit); + return status; +} + +/* 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: + */ diff --git a/src/switch_loadable_module.c b/src/switch_loadable_module.c index 90f49ae58e..97cb5e8d70 100644 --- a/src/switch_loadable_module.c +++ b/src/switch_loadable_module.c @@ -71,6 +71,7 @@ struct switch_loadable_module_container { switch_hash_t *chat_hash; switch_hash_t *say_hash; switch_hash_t *management_hash; + switch_hash_t *limit_hash; switch_mutex_t *mutex; switch_memory_pool_t *pool; }; @@ -433,6 +434,32 @@ static switch_status_t switch_loadable_module_process(char *key, switch_loadable } } } + if (new_module->module_interface->limit_interface) { + const switch_limit_interface_t *ptr; + + for (ptr = new_module->module_interface->limit_interface; ptr; ptr = ptr->next) { + if (!ptr->interface_name) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to load limit interface from %s due to no interface name.\n", key); + } else { + if (switch_core_hash_find(loadable_modules.limit_hash, ptr->interface_name)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, + "Failed to load limit interface %s. Name %s already exists\n", key, ptr->interface_name); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, + "Adding Limit interface '%s'\n", ptr->interface_name); + switch_core_hash_insert(loadable_modules.limit_hash, ptr->interface_name, (const void *) ptr); + if (switch_event_create(&event, SWITCH_EVENT_MODULE_LOAD) == SWITCH_STATUS_SUCCESS) { + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "type", "limit"); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "name", ptr->interface_name); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "key", new_module->key); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "filename", new_module->filename); + switch_event_fire(&event); + } + } + + } + } + } switch_mutex_unlock(loadable_modules.mutex); return SWITCH_STATUS_SUCCESS; @@ -768,6 +795,23 @@ static switch_status_t switch_loadable_module_unprocess(switch_loadable_module_t } } + if (old_module->module_interface->limit_interface) { + const switch_limit_interface_t *ptr; + + for (ptr = old_module->module_interface->limit_interface; ptr; ptr = ptr->next) { + if (ptr->interface_name) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, + "Deleting Limit interface '%s'\n", ptr->interface_name); + switch_core_hash_delete(loadable_modules.limit_hash, ptr->interface_name); + if (switch_event_create(&event, SWITCH_EVENT_MODULE_UNLOAD) == SWITCH_STATUS_SUCCESS) { + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "type", "limit"); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "name", ptr->interface_name); + switch_event_fire(&event); + } + } + } + } + switch_mutex_unlock(loadable_modules.mutex); return SWITCH_STATUS_SUCCESS; @@ -1164,6 +1208,7 @@ SWITCH_DECLARE(switch_status_t) switch_loadable_module_init() switch_core_hash_init_nocase(&loadable_modules.chat_hash, loadable_modules.pool); switch_core_hash_init_nocase(&loadable_modules.say_hash, loadable_modules.pool); switch_core_hash_init_nocase(&loadable_modules.management_hash, loadable_modules.pool); + switch_core_hash_init_nocase(&loadable_modules.limit_hash, loadable_modules.pool); switch_core_hash_init_nocase(&loadable_modules.dialplan_hash, loadable_modules.pool); switch_mutex_init(&loadable_modules.mutex, SWITCH_MUTEX_NESTED, loadable_modules.pool); @@ -1354,6 +1399,7 @@ SWITCH_DECLARE(void) switch_loadable_module_shutdown(void) switch_core_hash_destroy(&loadable_modules.chat_hash); switch_core_hash_destroy(&loadable_modules.say_hash); switch_core_hash_destroy(&loadable_modules.management_hash); + switch_core_hash_destroy(&loadable_modules.limit_hash); switch_core_hash_destroy(&loadable_modules.dialplan_hash); switch_core_destroy_memory_pool(&loadable_modules.pool); @@ -1411,17 +1457,18 @@ SWITCH_DECLARE(switch_codec_interface_t *) switch_loadable_module_get_codec_inte } HASH_FUNC(dialplan) - HASH_FUNC(timer) - HASH_FUNC(application) - HASH_FUNC(api) - HASH_FUNC(file) - HASH_FUNC(speech) - HASH_FUNC(asr) - HASH_FUNC(directory) - HASH_FUNC(chat) +HASH_FUNC(timer) +HASH_FUNC(application) +HASH_FUNC(api) +HASH_FUNC(file) +HASH_FUNC(speech) +HASH_FUNC(asr) +HASH_FUNC(directory) +HASH_FUNC(chat) +HASH_FUNC(limit) - SWITCH_DECLARE(switch_say_interface_t *) switch_loadable_module_get_say_interface(const char *name) +SWITCH_DECLARE(switch_say_interface_t *) switch_loadable_module_get_say_interface(const char *name) { return switch_core_hash_find_locked(loadable_modules.say_hash, name, loadable_modules.mutex); } @@ -1691,6 +1738,9 @@ SWITCH_DECLARE(void *) switch_loadable_module_create_interface(switch_loadable_m case SWITCH_MANAGEMENT_INTERFACE: ALLOC_INTERFACE(management) + case SWITCH_LIMIT_INTERFACE: + ALLOC_INTERFACE(limit) + default: switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid Module Type!\n"); return NULL;