sync changes in branch rupa_limit
This commit is contained in:
parent
f6eda2c23b
commit
0e8a26f840
|
@ -173,6 +173,7 @@ library_include_HEADERS = \
|
||||||
libs/libteletone/src/libteletone_detect.h \
|
libs/libteletone/src/libteletone_detect.h \
|
||||||
libs/libteletone/src/libteletone_generate.h \
|
libs/libteletone/src/libteletone_generate.h \
|
||||||
libs/libteletone/src/libteletone.h \
|
libs/libteletone/src/libteletone.h \
|
||||||
|
src/include/switch_limit.h \
|
||||||
src/include/switch_odbc.h
|
src/include/switch_odbc.h
|
||||||
|
|
||||||
nodist_libfreeswitch_la_SOURCES = \
|
nodist_libfreeswitch_la_SOURCES = \
|
||||||
|
@ -228,6 +229,7 @@ libfreeswitch_la_SOURCES = \
|
||||||
src/switch_config.c \
|
src/switch_config.c \
|
||||||
src/switch_time.c \
|
src/switch_time.c \
|
||||||
src/switch_odbc.c \
|
src/switch_odbc.c \
|
||||||
|
src/switch_limit.c \
|
||||||
src/g711.c \
|
src/g711.c \
|
||||||
src/switch_pcm.c \
|
src/switch_pcm.c \
|
||||||
src/switch_profile.c\
|
src/switch_profile.c\
|
||||||
|
|
|
@ -9,10 +9,11 @@ applications/mod_enum
|
||||||
#applications/mod_osp
|
#applications/mod_osp
|
||||||
applications/mod_fifo
|
applications/mod_fifo
|
||||||
#applications/mod_curl
|
#applications/mod_curl
|
||||||
|
applications/mod_db
|
||||||
|
applications/mod_hash
|
||||||
applications/mod_voicemail
|
applications/mod_voicemail
|
||||||
#applications/mod_directory
|
#applications/mod_directory
|
||||||
#applications/mod_lcr
|
#applications/mod_lcr
|
||||||
applications/mod_limit
|
|
||||||
applications/mod_expr
|
applications/mod_expr
|
||||||
applications/mod_esf
|
applications/mod_esf
|
||||||
#applications/mod_easyroute
|
#applications/mod_easyroute
|
||||||
|
|
|
@ -133,6 +133,7 @@
|
||||||
#include "switch_nat.h"
|
#include "switch_nat.h"
|
||||||
#include "switch_odbc.h"
|
#include "switch_odbc.h"
|
||||||
#include "switch_json.h"
|
#include "switch_json.h"
|
||||||
|
#include "switch_limit.h"
|
||||||
|
|
||||||
#include <libteletone.h>
|
#include <libteletone.h>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
/*
|
||||||
|
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||||
|
* Copyright (C) 2005-2010, Anthony Minessale II <anthm@freeswitch.org>
|
||||||
|
*
|
||||||
|
* 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 <anthm@freeswitch.org>
|
||||||
|
* Portions created by the Initial Developer are Copyright (C)
|
||||||
|
* the Initial Developer. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Contributor(s):
|
||||||
|
*
|
||||||
|
* Rupa Schomaker <rupa@rupa.com>
|
||||||
|
*
|
||||||
|
* 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:
|
||||||
|
*/
|
|
@ -81,6 +81,8 @@ SWITCH_BEGIN_EXTERN_C
|
||||||
switch_asr_interface_t *asr_interface;
|
switch_asr_interface_t *asr_interface;
|
||||||
/*! the table of management interfaces the module has implmented */
|
/*! the table of management interfaces the module has implmented */
|
||||||
switch_management_interface_t *management_interface;
|
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;
|
switch_thread_rwlock_t *rwlock;
|
||||||
int refs;
|
int refs;
|
||||||
switch_memory_pool_t *pool;
|
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);
|
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
|
\brief Retrieve the list of loaded codecs into an array
|
||||||
\param array the array to populate
|
\param array the array to populate
|
||||||
|
@ -315,6 +324,18 @@ SWITCH_MOD_DECLARE(switch_status_t) switch_module_shutdown(void);
|
||||||
break; \
|
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);
|
SWITCH_DECLARE(uint32_t) switch_core_codec_next_id(void);
|
||||||
|
|
||||||
#define SWITCH_ADD_CODEC(codec_int, int_name) \
|
#define SWITCH_ADD_CODEC(codec_int, int_name) \
|
||||||
|
|
|
@ -512,6 +512,28 @@ struct switch_management_interface {
|
||||||
struct switch_management_interface *next;
|
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 */
|
/*! \brief Abstract interface to a directory module */
|
||||||
struct switch_directory_interface {
|
struct switch_directory_interface {
|
||||||
/*! the name of the interface */
|
/*! the name of the interface */
|
||||||
|
|
|
@ -267,7 +267,8 @@ typedef enum {
|
||||||
SWITCH_CHAT_INTERFACE,
|
SWITCH_CHAT_INTERFACE,
|
||||||
SWITCH_SAY_INTERFACE,
|
SWITCH_SAY_INTERFACE,
|
||||||
SWITCH_ASR_INTERFACE,
|
SWITCH_ASR_INTERFACE,
|
||||||
SWITCH_MANAGEMENT_INTERFACE
|
SWITCH_MANAGEMENT_INTERFACE,
|
||||||
|
SWITCH_LIMIT_INTERFACE
|
||||||
} switch_module_interface_name_t;
|
} switch_module_interface_name_t;
|
||||||
|
|
||||||
typedef enum {
|
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_management_interface switch_management_interface_t;
|
||||||
typedef struct switch_core_port_allocator switch_core_port_allocator_t;
|
typedef struct switch_core_port_allocator switch_core_port_allocator_t;
|
||||||
typedef struct switch_media_bug switch_media_bug_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 {
|
struct switch_console_callback_match_node {
|
||||||
char *val;
|
char *val;
|
||||||
|
|
|
@ -3358,7 +3358,7 @@ SWITCH_STANDARD_API(alias_function)
|
||||||
return SWITCH_STATUS_SUCCESS;
|
return SWITCH_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define SHOW_SYNTAX "codec|endpoint|application|api|dialplan|file|timer|calls [count]|channels [count|like <match string>]|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 <match string>]|distinct_channels|aliases|complete|chat|management|modules|nat_map|say|interfaces|interface_types|tasks|limits"
|
||||||
SWITCH_STANDARD_API(show_function)
|
SWITCH_STANDARD_API(show_function)
|
||||||
{
|
{
|
||||||
char sql[1024];
|
char sql[1024];
|
||||||
|
@ -3421,6 +3421,7 @@ SWITCH_STANDARD_API(show_function)
|
||||||
!strncasecmp(command, "file", 4) ||
|
!strncasecmp(command, "file", 4) ||
|
||||||
!strncasecmp(command, "timer", 5) ||
|
!strncasecmp(command, "timer", 5) ||
|
||||||
!strncasecmp(command, "chat", 4) ||
|
!strncasecmp(command, "chat", 4) ||
|
||||||
|
!strncasecmp(command, "limit", 5) ||
|
||||||
!strncasecmp(command, "say", 3) || !strncasecmp(command, "management", 10) || !strncasecmp(command, "endpoint", 8)) {
|
!strncasecmp(command, "say", 3) || !strncasecmp(command, "management", 10) || !strncasecmp(command, "endpoint", 8)) {
|
||||||
if (end_of(command) == 's') {
|
if (end_of(command) == 's') {
|
||||||
end_of(command) = '\0';
|
end_of(command) = '\0';
|
||||||
|
@ -4222,6 +4223,107 @@ SWITCH_STANDARD_API(sql_escape)
|
||||||
return SWITCH_STATUS_SUCCESS;
|
return SWITCH_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* LIMIT Stuff */
|
||||||
|
#define LIMIT_USAGE_USAGE "<backend> <realm> <id> [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 "<backend>"
|
||||||
|
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 "<backend>"
|
||||||
|
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)
|
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_commands_shutdown)
|
||||||
{
|
{
|
||||||
int x;
|
int x;
|
||||||
|
@ -4310,6 +4412,9 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_commands_load)
|
||||||
SWITCH_ADD_API(commands_api_interface, "hupall", "hupall", hupall_api_function, "<cause> [<var> <value>]");
|
SWITCH_ADD_API(commands_api_interface, "hupall", "hupall", hupall_api_function, "<cause> [<var> <value>]");
|
||||||
SWITCH_ADD_API(commands_api_interface, "in_group", "determine if a user is in a group", in_group_function, "<user>[@<domain>] <group_name>");
|
SWITCH_ADD_API(commands_api_interface, "in_group", "determine if a user is in a group", in_group_function, "<user>[@<domain>] <group_name>");
|
||||||
SWITCH_ADD_API(commands_api_interface, "is_lan_addr", "see if an ip is a lan addr", lan_addr_function, "<ip>");
|
SWITCH_ADD_API(commands_api_interface, "is_lan_addr", "see if an ip is a lan addr", lan_addr_function, "<ip>");
|
||||||
|
SWITCH_ADD_API(commands_api_interface, "limit_usage", "Gets the usage count of a limited resource", limit_usage_function, "<backend> <realm> <id>");
|
||||||
|
SWITCH_ADD_API(commands_api_interface, "limit_status", "Gets the status of a limit backend", limit_status_function, "<backend>");
|
||||||
|
SWITCH_ADD_API(commands_api_interface, "limit_reset", "Reset the counters of a limit backend", limit_reset_function, "<backend>");
|
||||||
SWITCH_ADD_API(commands_api_interface, "load", "Load Module", load_function, LOAD_SYNTAX);
|
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, "log", "Log", log_function, LOG_SYNTAX);
|
||||||
SWITCH_ADD_API(commands_api_interface, "md5", "md5", md5_function, "<data>");
|
SWITCH_ADD_API(commands_api_interface, "md5", "md5", md5_function, "<data>");
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
<VisualStudioProject
|
<VisualStudioProject
|
||||||
ProjectType="Visual C++"
|
ProjectType="Visual C++"
|
||||||
Version="9.00"
|
Version="9.00"
|
||||||
Name="mod_limit"
|
Name="mod_db"
|
||||||
ProjectGUID="{F6A33240-8F29-48BD-98F0-826995911799}"
|
ProjectGUID="{F6A33240-8F29-48BD-98F0-826995911799}"
|
||||||
RootNamespace="mod_limit"
|
RootNamespace="mod_db"
|
||||||
Keyword="Win32Proj"
|
Keyword="Win32Proj"
|
||||||
TargetFrameworkVersion="131072"
|
TargetFrameworkVersion="131072"
|
||||||
>
|
>
|
||||||
|
@ -274,7 +274,7 @@
|
||||||
</References>
|
</References>
|
||||||
<Files>
|
<Files>
|
||||||
<File
|
<File
|
||||||
RelativePath=".\mod_limit.c"
|
RelativePath=".\mod_db.c"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
</Files>
|
</Files>
|
|
@ -0,0 +1,663 @@
|
||||||
|
/*
|
||||||
|
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||||
|
* Copyright (C) 2005-2010, Anthony Minessale II <anthm@freeswitch.org>
|
||||||
|
*
|
||||||
|
* 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 <anthm@freeswitch.org>
|
||||||
|
* Portions created by the Initial Developer are Copyright (C)
|
||||||
|
* the Initial Developer. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Contributor(s):
|
||||||
|
*
|
||||||
|
* Anthony Minessale II <anthm@freeswitch.org>
|
||||||
|
* Ken Rice <krice at suspicious dot org
|
||||||
|
* Mathieu Rene <mathieu.rene@gmail.com>
|
||||||
|
* Bret McDanel <trixter AT 0xdecafbad.com>
|
||||||
|
* Rupa Schomaker <rupa@rupa.com>
|
||||||
|
*
|
||||||
|
* mod_db.c -- Implements simple db API, group support, and limit db backend
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
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]/<realm>/<key>/<val>"
|
||||||
|
#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]:<group name>:<val>"
|
||||||
|
#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]/<realm>/<key>/<value>");
|
||||||
|
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]:<group name>:<url>");
|
||||||
|
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:
|
||||||
|
*/
|
|
@ -3007,6 +3007,140 @@ SWITCH_STANDARD_APP(session_loglevel_function)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* LIMIT STUFF */
|
||||||
|
#define LIMIT_USAGE "<backend> <realm> <id> [<max>[/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 "<backend> <realm> <id> [<max>[/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 SPEAK_DESC "Speak text to a channel via the tts interface"
|
||||||
#define DISPLACE_DESC "Displace audio from a file to the channels input"
|
#define DISPLACE_DESC "Displace audio from a file to the channels input"
|
||||||
#define SESS_REC_DESC "Starts a background recording of the entire session"
|
#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);
|
SAF_NONE);
|
||||||
SWITCH_ADD_APP(app_interface, "session_loglevel", "session_loglevel", "session_loglevel", session_loglevel_function, SESSION_LOGLEVEL_SYNTAX,
|
SWITCH_ADD_APP(app_interface, "session_loglevel", "session_loglevel", "session_loglevel", session_loglevel_function, SESSION_LOGLEVEL_SYNTAX,
|
||||||
SAF_SUPPORT_NOMEDIA);
|
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);
|
SWITCH_ADD_DIALPLAN(dp_interface, "inline", inline_dialplan_hunt);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,283 @@
|
||||||
|
<?xml version="1.0" encoding="Windows-1252"?>
|
||||||
|
<VisualStudioProject
|
||||||
|
ProjectType="Visual C++"
|
||||||
|
Version="9.00"
|
||||||
|
Name="mod_hash"
|
||||||
|
ProjectGUID="{F6A33240-8F29-48BD-98F0-826995911799}"
|
||||||
|
RootNamespace="mod_hash"
|
||||||
|
Keyword="Win32Proj"
|
||||||
|
TargetFrameworkVersion="131072"
|
||||||
|
>
|
||||||
|
<Platforms>
|
||||||
|
<Platform
|
||||||
|
Name="Win32"
|
||||||
|
/>
|
||||||
|
<Platform
|
||||||
|
Name="x64"
|
||||||
|
/>
|
||||||
|
</Platforms>
|
||||||
|
<ToolFiles>
|
||||||
|
</ToolFiles>
|
||||||
|
<Configurations>
|
||||||
|
<Configuration
|
||||||
|
Name="Debug|Win32"
|
||||||
|
ConfigurationType="2"
|
||||||
|
InheritedPropertySheets="..\..\..\..\w32\module_debug.vsprops"
|
||||||
|
CharacterSet="2"
|
||||||
|
>
|
||||||
|
<Tool
|
||||||
|
Name="VCPreBuildEventTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCCustomBuildTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCXMLDataGeneratorTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCWebServiceProxyGeneratorTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCMIDLTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCCLCompilerTool"
|
||||||
|
UsePrecompiledHeader="0"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCManagedResourceCompilerTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCResourceCompilerTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCPreLinkEventTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCLinkerTool"
|
||||||
|
RandomizedBaseAddress="1"
|
||||||
|
DataExecutionPrevention="0"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCALinkTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCManifestTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCXDCMakeTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCBscMakeTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCFxCopTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCAppVerifierTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCPostBuildEventTool"
|
||||||
|
/>
|
||||||
|
</Configuration>
|
||||||
|
<Configuration
|
||||||
|
Name="Debug|x64"
|
||||||
|
ConfigurationType="2"
|
||||||
|
InheritedPropertySheets="..\..\..\..\w32\module_debug.vsprops"
|
||||||
|
CharacterSet="2"
|
||||||
|
>
|
||||||
|
<Tool
|
||||||
|
Name="VCPreBuildEventTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCCustomBuildTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCXMLDataGeneratorTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCWebServiceProxyGeneratorTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCMIDLTool"
|
||||||
|
TargetEnvironment="3"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCCLCompilerTool"
|
||||||
|
UsePrecompiledHeader="0"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCManagedResourceCompilerTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCResourceCompilerTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCPreLinkEventTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCLinkerTool"
|
||||||
|
OutputFile="$(SolutionDir)$(PlatformName)\$(ConfigurationName)/mod/$(ProjectName).dll"
|
||||||
|
RandomizedBaseAddress="1"
|
||||||
|
DataExecutionPrevention="0"
|
||||||
|
TargetMachine="17"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCALinkTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCManifestTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCXDCMakeTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCBscMakeTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCFxCopTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCAppVerifierTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCPostBuildEventTool"
|
||||||
|
/>
|
||||||
|
</Configuration>
|
||||||
|
<Configuration
|
||||||
|
Name="Release|Win32"
|
||||||
|
ConfigurationType="2"
|
||||||
|
InheritedPropertySheets="..\..\..\..\w32\module_release.vsprops"
|
||||||
|
CharacterSet="2"
|
||||||
|
>
|
||||||
|
<Tool
|
||||||
|
Name="VCPreBuildEventTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCCustomBuildTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCXMLDataGeneratorTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCWebServiceProxyGeneratorTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCMIDLTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCCLCompilerTool"
|
||||||
|
UsePrecompiledHeader="0"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCManagedResourceCompilerTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCResourceCompilerTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCPreLinkEventTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCLinkerTool"
|
||||||
|
RandomizedBaseAddress="1"
|
||||||
|
DataExecutionPrevention="0"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCALinkTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCManifestTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCXDCMakeTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCBscMakeTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCFxCopTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCAppVerifierTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCPostBuildEventTool"
|
||||||
|
/>
|
||||||
|
</Configuration>
|
||||||
|
<Configuration
|
||||||
|
Name="Release|x64"
|
||||||
|
ConfigurationType="2"
|
||||||
|
InheritedPropertySheets="..\..\..\..\w32\module_release.vsprops"
|
||||||
|
CharacterSet="2"
|
||||||
|
>
|
||||||
|
<Tool
|
||||||
|
Name="VCPreBuildEventTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCCustomBuildTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCXMLDataGeneratorTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCWebServiceProxyGeneratorTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCMIDLTool"
|
||||||
|
TargetEnvironment="3"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCCLCompilerTool"
|
||||||
|
UsePrecompiledHeader="0"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCManagedResourceCompilerTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCResourceCompilerTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCPreLinkEventTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCLinkerTool"
|
||||||
|
OutputFile="$(SolutionDir)$(PlatformName)\$(ConfigurationName)/mod/$(ProjectName).dll"
|
||||||
|
RandomizedBaseAddress="1"
|
||||||
|
DataExecutionPrevention="0"
|
||||||
|
TargetMachine="17"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCALinkTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCManifestTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCXDCMakeTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCBscMakeTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCFxCopTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCAppVerifierTool"
|
||||||
|
/>
|
||||||
|
<Tool
|
||||||
|
Name="VCPostBuildEventTool"
|
||||||
|
/>
|
||||||
|
</Configuration>
|
||||||
|
</Configurations>
|
||||||
|
<References>
|
||||||
|
</References>
|
||||||
|
<Files>
|
||||||
|
<File
|
||||||
|
RelativePath=".\mod_hash.c"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
|
</Files>
|
||||||
|
<Globals>
|
||||||
|
</Globals>
|
||||||
|
</VisualStudioProject>
|
|
@ -0,0 +1,506 @@
|
||||||
|
/*
|
||||||
|
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||||
|
* Copyright (C) 2005-2010, Anthony Minessale II <anthm@freeswitch.org>
|
||||||
|
*
|
||||||
|
* 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 <anthm@freeswitch.org>
|
||||||
|
* Portions created by the Initial Developer are Copyright (C)
|
||||||
|
* the Initial Developer. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Contributor(s):
|
||||||
|
*
|
||||||
|
* Anthony Minessale II <anthm@freeswitch.org>
|
||||||
|
* Ken Rice <krice at suspicious dot org
|
||||||
|
* Mathieu Rene <mathieu.rene@gmail.com>
|
||||||
|
* Bret McDanel <trixter AT 0xdecafbad.com>
|
||||||
|
* Rupa Schomaker <rupa@rupa.com>
|
||||||
|
*
|
||||||
|
* mod_hash.c -- Hash api, hash backend for limit
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
#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]/<realm>/<key>/<val>"
|
||||||
|
#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]/<realm>/<key>/<value>");
|
||||||
|
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:
|
||||||
|
*/
|
|
@ -126,7 +126,6 @@ struct profile_obj {
|
||||||
switch_bool_t reorder_by_rate;
|
switch_bool_t reorder_by_rate;
|
||||||
switch_bool_t quote_in_list;
|
switch_bool_t quote_in_list;
|
||||||
switch_bool_t info_in_headers;
|
switch_bool_t info_in_headers;
|
||||||
switch_bool_t enable_sip_redir;
|
|
||||||
};
|
};
|
||||||
typedef struct profile_obj profile_t;
|
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);
|
user_rate = switch_core_sprintf(pool, ",lcr_user_rate=%s", cur_route->user_rate_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profile->enable_sip_redir) {
|
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,
|
||||||
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);
|
header, 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Returning Dialstring %s\n", data);
|
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Returning Dialstring %s\n", data);
|
||||||
return data;
|
return data;
|
||||||
|
@ -415,7 +405,7 @@ static switch_bool_t db_check(char *sql)
|
||||||
switch_cache_db_handle_t *dbh = NULL;
|
switch_cache_db_handle_t *dbh = NULL;
|
||||||
|
|
||||||
if (globals.odbc_dsn && (dbh = lcr_get_db_handle())) {
|
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;
|
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;
|
switch_cache_db_handle_t *dbh = NULL;
|
||||||
|
|
||||||
if (globals.odbc_dsn && (dbh = lcr_get_db_handle())) {
|
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;
|
retval = SWITCH_FALSE;
|
||||||
} else {
|
} else {
|
||||||
retval = SWITCH_TRUE;
|
retval = SWITCH_TRUE;
|
||||||
|
@ -912,7 +903,6 @@ static switch_status_t lcr_load_config()
|
||||||
char *reorder_by_rate = NULL;
|
char *reorder_by_rate = NULL;
|
||||||
char *quote_in_list = NULL;
|
char *quote_in_list = NULL;
|
||||||
char *info_in_headers = NULL;
|
char *info_in_headers = NULL;
|
||||||
char *enable_sip_redir = NULL;
|
|
||||||
char *id_s = NULL;
|
char *id_s = NULL;
|
||||||
char *custom_sql = NULL;
|
char *custom_sql = NULL;
|
||||||
int argc, x = 0;
|
int argc, x = 0;
|
||||||
|
@ -965,8 +955,6 @@ static switch_status_t lcr_load_config()
|
||||||
info_in_headers = val;
|
info_in_headers = val;
|
||||||
} else if (!strcasecmp(var, "quote_in_list") && !zstr(val)) {
|
} else if (!strcasecmp(var, "quote_in_list") && !zstr(val)) {
|
||||||
quote_in_list = 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);
|
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)) {
|
if (!zstr(quote_in_list)) {
|
||||||
profile->quote_in_list = switch_true(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);
|
switch_channel_set_variable(channel, vbuf, cur_route->codec);
|
||||||
cnt++;
|
cnt++;
|
||||||
if (cur_route->next) {
|
if (cur_route->next) {
|
||||||
if (routes.profile->enable_sip_redir) {
|
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);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
dig_stream.write_function(&dig_stream, "%s", cur_route->dialstring);
|
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, " 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, " 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, " 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, " Quote IN() List:\t%s\n", profile->quote_in_list ? "enabled" : "disabled");
|
||||||
stream->write_function(stream, "\n");
|
stream->write_function(stream, "\n");
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,153 +0,0 @@
|
||||||
<?xml version="1.0" encoding="Windows-1252"?>
|
|
||||||
<VisualStudioProject
|
|
||||||
ProjectType="Visual C++"
|
|
||||||
Version="8.00"
|
|
||||||
Name="mod_limit"
|
|
||||||
ProjectGUID="{F6A33240-8F29-48BD-98F0-826995911799}"
|
|
||||||
RootNamespace="mod_limit"
|
|
||||||
Keyword="Win32Proj"
|
|
||||||
>
|
|
||||||
<Platforms>
|
|
||||||
<Platform
|
|
||||||
Name="Win32"
|
|
||||||
/>
|
|
||||||
</Platforms>
|
|
||||||
<ToolFiles>
|
|
||||||
</ToolFiles>
|
|
||||||
<Configurations>
|
|
||||||
<Configuration
|
|
||||||
Name="Debug|Win32"
|
|
||||||
ConfigurationType="2"
|
|
||||||
InheritedPropertySheets="..\..\..\..\w32\module_debug.vsprops"
|
|
||||||
CharacterSet="2"
|
|
||||||
>
|
|
||||||
<Tool
|
|
||||||
Name="VCPreBuildEventTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCCustomBuildTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCXMLDataGeneratorTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCWebServiceProxyGeneratorTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCMIDLTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCCLCompilerTool"
|
|
||||||
UsePrecompiledHeader="0"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCManagedResourceCompilerTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCResourceCompilerTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCPreLinkEventTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCLinkerTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCALinkTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCManifestTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCXDCMakeTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCBscMakeTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCFxCopTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCAppVerifierTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCWebDeploymentTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCPostBuildEventTool"
|
|
||||||
/>
|
|
||||||
</Configuration>
|
|
||||||
<Configuration
|
|
||||||
Name="Release|Win32"
|
|
||||||
ConfigurationType="2"
|
|
||||||
InheritedPropertySheets="..\..\..\..\w32\module_release.vsprops"
|
|
||||||
CharacterSet="2"
|
|
||||||
>
|
|
||||||
<Tool
|
|
||||||
Name="VCPreBuildEventTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCCustomBuildTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCXMLDataGeneratorTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCWebServiceProxyGeneratorTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCMIDLTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCCLCompilerTool"
|
|
||||||
UsePrecompiledHeader="0"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCManagedResourceCompilerTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCResourceCompilerTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCPreLinkEventTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCLinkerTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCALinkTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCManifestTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCXDCMakeTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCBscMakeTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCFxCopTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCAppVerifierTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCWebDeploymentTool"
|
|
||||||
/>
|
|
||||||
<Tool
|
|
||||||
Name="VCPostBuildEventTool"
|
|
||||||
/>
|
|
||||||
</Configuration>
|
|
||||||
</Configurations>
|
|
||||||
<References>
|
|
||||||
</References>
|
|
||||||
<Files>
|
|
||||||
<File
|
|
||||||
RelativePath=".\mod_limit.c"
|
|
||||||
>
|
|
||||||
</File>
|
|
||||||
</Files>
|
|
||||||
<Globals>
|
|
||||||
</Globals>
|
|
||||||
</VisualStudioProject>
|
|
|
@ -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 */
|
/* 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))) {
|
if (channel && (export_vars = switch_channel_get_variable(channel, SWITCH_EXPORT_VARS_VARIABLE))) {
|
||||||
char *cptmp = switch_core_session_strdup(session, export_vars);
|
char *cptmp = switch_core_session_strdup(session, export_vars);
|
||||||
|
|
|
@ -0,0 +1,222 @@
|
||||||
|
/*
|
||||||
|
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
|
||||||
|
* Copyright (C) 2005-2009, Anthony Minessale II <anthm@freeswitch.org>
|
||||||
|
*
|
||||||
|
* 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 <anthm@freeswitch.org>
|
||||||
|
* Portions created by the Initial Developer are Copyright (C)
|
||||||
|
* the Initial Developer. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Contributor(s):
|
||||||
|
*
|
||||||
|
* Rupa Schomaker <rupa@rupa.com>
|
||||||
|
*
|
||||||
|
* switch_limit.c Limit support
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <switch.h>
|
||||||
|
|
||||||
|
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:
|
||||||
|
*/
|
|
@ -71,6 +71,7 @@ struct switch_loadable_module_container {
|
||||||
switch_hash_t *chat_hash;
|
switch_hash_t *chat_hash;
|
||||||
switch_hash_t *say_hash;
|
switch_hash_t *say_hash;
|
||||||
switch_hash_t *management_hash;
|
switch_hash_t *management_hash;
|
||||||
|
switch_hash_t *limit_hash;
|
||||||
switch_mutex_t *mutex;
|
switch_mutex_t *mutex;
|
||||||
switch_memory_pool_t *pool;
|
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);
|
switch_mutex_unlock(loadable_modules.mutex);
|
||||||
return SWITCH_STATUS_SUCCESS;
|
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);
|
switch_mutex_unlock(loadable_modules.mutex);
|
||||||
|
|
||||||
return SWITCH_STATUS_SUCCESS;
|
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.chat_hash, loadable_modules.pool);
|
||||||
switch_core_hash_init_nocase(&loadable_modules.say_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.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_core_hash_init_nocase(&loadable_modules.dialplan_hash, loadable_modules.pool);
|
||||||
switch_mutex_init(&loadable_modules.mutex, SWITCH_MUTEX_NESTED, 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.chat_hash);
|
||||||
switch_core_hash_destroy(&loadable_modules.say_hash);
|
switch_core_hash_destroy(&loadable_modules.say_hash);
|
||||||
switch_core_hash_destroy(&loadable_modules.management_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_hash_destroy(&loadable_modules.dialplan_hash);
|
||||||
|
|
||||||
switch_core_destroy_memory_pool(&loadable_modules.pool);
|
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(dialplan)
|
||||||
HASH_FUNC(timer)
|
HASH_FUNC(timer)
|
||||||
HASH_FUNC(application)
|
HASH_FUNC(application)
|
||||||
HASH_FUNC(api)
|
HASH_FUNC(api)
|
||||||
HASH_FUNC(file)
|
HASH_FUNC(file)
|
||||||
HASH_FUNC(speech)
|
HASH_FUNC(speech)
|
||||||
HASH_FUNC(asr)
|
HASH_FUNC(asr)
|
||||||
HASH_FUNC(directory)
|
HASH_FUNC(directory)
|
||||||
HASH_FUNC(chat)
|
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);
|
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:
|
case SWITCH_MANAGEMENT_INTERFACE:
|
||||||
ALLOC_INTERFACE(management)
|
ALLOC_INTERFACE(management)
|
||||||
|
|
||||||
|
case SWITCH_LIMIT_INTERFACE:
|
||||||
|
ALLOC_INTERFACE(limit)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid Module Type!\n");
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid Module Type!\n");
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
Loading…
Reference in New Issue