diff --git a/build/modules.conf.in b/build/modules.conf.in index 84c419824e..e57c090b8b 100644 --- a/build/modules.conf.in +++ b/build/modules.conf.in @@ -6,6 +6,7 @@ applications/mod_commands applications/mod_conference applications/mod_dptools applications/mod_enum +#applications/mod_osp applications/mod_fifo #applications/mod_fax #applications/mod_curl diff --git a/conf/autoload_configs/osp.conf.xml b/conf/autoload_configs/osp.conf.xml new file mode 100644 index 0000000000..168490b3db --- /dev/null +++ b/conf/autoload_configs/osp.conf.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/mod/applications/mod_osp/Makefile.am b/src/mod/applications/mod_osp/Makefile.am new file mode 100644 index 0000000000..360492b009 --- /dev/null +++ b/src/mod/applications/mod_osp/Makefile.am @@ -0,0 +1,8 @@ +include $(top_srcdir)/build/modmake.rulesam +MODNAME=mod_osp + +mod_LTLIBRARIES = mod_osp.la +mod_osp_la_SOURCES = mod_osp.c +mod_osp_la_CFLAGS = $(AM_CFLAGS) +mod_osp_la_LDFLAGS = -avoid-version -module -no-undefined -shared -losptk -lssl -lcrypto -lpthread -lm + diff --git a/src/mod/applications/mod_osp/mod_osp.c b/src/mod/applications/mod_osp/mod_osp.c new file mode 100644 index 0000000000..74af525a49 --- /dev/null +++ b/src/mod/applications/mod_osp/mod_osp.c @@ -0,0 +1,2197 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2009, Anthony Minessale II + * + * Version: MPL 1.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * + * The Initial Developer of the Original Code is + * Anthony Minessale II + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Di-Shi Sun + * + * mod_osp.c -- Open Settlement Protocol (OSP) Module + * + */ + +/* + * TODO: + * 1. NID -> outbound messages + */ + +#include +#include +#include +#include + +/* OSP Buffer Size Constants */ +#define OSP_SIZE_NORSTR 256 /* OSP normal string buffer size */ +#define OSP_SIZE_KEYSTR 1024 /* OSP certificate string buffer size */ +#define OSP_SIZE_ROUSTR 1024 /* OSP route buffer size */ +#define OSP_SIZE_TOKSTR 4096 /* OSP token string buffer size */ + +/* OSP Settings Constants */ +#define OSP_MAX_SP 8 /* Max number of OSP service points */ +#define OSP_AUDIT_URL "localhost" /* OSP default Audit URL */ +#define OSP_LOCAL_VALID 1 /* OSP token validating method, locally */ +#define OSP_DEF_LIFETIME 300 /* OSP default SSL lifetime */ +#define OSP_DEF_MAXCONN 20 /* OSP default max connections */ +#define OSP_MIN_MAXCONN 1 /* OSP min max connections */ +#define OSP_MAX_MAXCONN 1000 /* OSP max max connections */ +#define OSP_DEF_PERSIST 60 /* OSP default HTTP persistence in seconds */ +#define OSP_DEF_RETRYDELAY 0 /* OSP default retry delay in seconds */ +#define OSP_MIN_RETRYDELAY 0 /* OSP min retry delay */ +#define OSP_MAX_RETRYDELAY 10 /* OSP max retry delay */ +#define OSP_DEF_RETRYLIMIT 2 /* OSP default retry times */ +#define OSP_MIN_RETRYLIMIT 0 /* OSP min retry times */ +#define OSP_MAX_RETRYLIMIT 100 /* OSP max retry times */ +#define OSP_DEF_TIMEOUT 10000 /* OSP default timeout in ms */ +#define OSP_MIN_TIMEOUT 200 /* OSP min timeout in ms */ +#define OSP_MAX_TIMEOUT 60000 /* OSP max timeout in ms */ +#define OSP_CUSTOMER_ID "" /* OSP customer ID */ +#define OSP_DEVICE_ID "" /* OSP device ID */ +#define OSP_DEF_MAXDEST 5 /* OSP default max destinations */ +#define OSP_MIN_MAXDEST 1 /* OSP min max destinations */ +#define OSP_MAX_MAXDEST 10 /* OSP max max destinations */ +#define OSP_DEF_PROFILE "default" /* OSP default profile name */ +#define OSP_DEF_STRING "" /* OSP default empty string */ +#define OSP_DEF_CALLID "UNDEFINED" /* OSP default Call-ID */ +#define OSP_DEF_STATS -1 /* OSP default statistics */ +#define OSP_URI_DELIM '@' /* URI delimit */ +#define OSP_USER_DELIM ";:" /* URI userinfo delimit */ +#define OSP_HOST_DELIM ";>" /* URI hostport delimit */ +#define OSP_MAX_CINFO 8 /* Max number of custom info */ + +/* OSP Handle Constant */ +#define OSP_INVALID_HANDLE -1 /* Invalid OSP handle, provider, transaction etc. */ + +/* OSP Supported Destination Protocols for Default Protocol */ +#define OSP_PROTOCOL_SIP "sip" /* SIP protocol name */ +#define OSP_PROTOCOL_H323 "h323" /* H.323 protocol name */ +#define OSP_PROTOCOL_IAX "iax" /* IAX protocol name */ +#define OSP_PROTOCOL_SKYPE "skype" /* Skype protocol name */ +#define OSP_PROTOCOL_UNKNO "unknown" /* Unknown protocol */ +#define OSP_PROTOCOL_UNDEF "undefined" /* Undefined protocol */ +#define OSP_PROTOCOL_UNSUP "unsupported" /* Unsupported protocol */ + +/* OSP Supported Destination Protocols for Destination Protocol Usage */ +#define OSP_MODULE_SIP "mod_sofia" /* FreeSWITCH SIP module name */ +#define OSP_MODULE_H323 "mod_h323" /* FreeSWITCH H.323 module name */ +#define OSP_MODULE_IAX "mod_iax" /* FreeSWITCH IAX module name */ +#define OSP_MODULE_SKYPE "mod_skypopen" /* FreeSWITCH Skype module name */ + +/* OSP Variables Name */ +#define OSP_VAR_PROFILE "osp_profile" /* Provider name, in OSP cookie */ +#define OSP_VAR_TRANSID "osp_transaction_id" /* Transaction ID, in OSP cookie */ +#define OSP_VAR_START "osp_start_time" /* Inbound Call start time, in OSP cookie */ +#define OSP_VAR_DESTCOUNT "osp_destination_count" /* Destination count, in OSP cookie */ +#define OSP_VAR_SRCNID "osp_source_nid" /* Source network ID, inbound and in OSP cookie */ +#define OSP_VAR_DESTIP "osp_destination_ip" /* Destination IP, in OSP cookie */ +#define OSP_VAR_DESTNID "osp_destination_nid" /* Destination network ID, in OSP cookie */ +#define OSP_VAR_CUSTOMINFO "osp_custom_info_" /* Custom info */ +#define OSP_VAR_ROUTECOUNT "osp_route_count" /* Number of destinations */ +#define OSP_VAR_ROUTEPRE "osp_route_" /* Destination prefix */ +#define OSP_VAR_AUTOROUTE "osp_auto_route" /* Bridge route string */ + +/* OSP Use Variable Name */ +#define OSP_FS_TOHOST "sip_to_host" /* Inbound SIP To host */ +#define OSP_FS_TOPORT "sip_to_port" /* Inbound SIP To port */ +#define OSP_FS_DIVERSION "sip_h_Diversion" /* Inbound SIP Diversion header */ +#define OSP_FS_OUTCALLID "sip_call_id" /* Outbound SIP Call-ID */ +#define OSP_FS_OUTCALLING "origination_caller_id_number" /* Outbound calling number */ +#define OSP_FS_SIPRELEASE "sip_hangup_disposition" /* SIP release source */ +#define OSP_FS_DOWNCODEC "write_codec" /* Downstream codec */ +#define OSP_FS_UPCODEC "read_codec" /* Upstream codec */ +#define OSP_FS_RTPDOWNOCTS "rtp_audio_out_media_bytes" /* Downstream octets */ +#define OSP_FS_RTPUPOCTS "rtp_audio_in_media_bytes" /* Upstream octets */ +#define OSP_FS_RTPDOWNPKTS "rtp_audio_out_media_packet_count" /* Downstream packets */ +#define OSP_FS_RTPUPPKTS "rtp_audio_in_media_packet_count" /* Upstream packets */ + +typedef struct osp_settings { + switch_bool_t debug; /* OSP module debug info flag */ + switch_log_level_t loglevel; /* Log level for debug info */ + switch_bool_t hardware; /* Crypto hardware flag */ + const char *module[OSPC_DPROT_NUMBER]; /* Endpoint names */ + const char *profile[OSPC_DPROT_NUMBER]; /* Profile names */ + OSPE_DEST_PROTOCOL protocol; /* Default destination protocol */ + switch_bool_t shutdown; /* OSP module status */ + switch_memory_pool_t *pool; /* OSP module memory pool */ +} osp_settings_t; + +/* OSP Service Types */ +typedef enum osp_srvtype { + OSP_SRV_VOICE = 0, /* Normal voice service */ + OSP_SRV_NPQUERY /* Number portability query service */ +} osp_srvtype_t; + +typedef struct osp_provider { + const char *name; /* OSP provider profile name */ + int spnum; /* Number of OSP service points */ + const char *spurls[OSP_MAX_SP]; /* OSP provider service point URLs */ + const char *device; /* OSP source IP */ + int lifetime; /* SSL life time */ + int maxconnect; /* Max number of HTTP connections */ + int persistence; /* HTTP persistence in seconds */ + int retrydelay; /* HTTP retry delay in seconds */ + int retrylimit; /* HTTP retry times */ + int timeout; /* HTTP timeout in ms */ + osp_srvtype_t srvtype; /* OSP service type */ + int maxdest; /* Max destinations */ + switch_bool_t userphone; /* Add "user=phone" URI parameter */ + OSPTPROVHANDLE handle; /* OSP provider handle */ + struct osp_provider *next; /* Next OSP provider */ +} osp_provider_t; + +typedef struct osp_inbound { + const char *srcdev; /* Source device IP address */ + const char *calling; /* Inbound calling number */ + char called[OSP_SIZE_NORSTR]; /* Inbound called number */ + char nprn[OSP_SIZE_NORSTR]; /* Inbound NP routing number */ + char npcic[OSP_SIZE_NORSTR]; /* Inbound NP carrier identification code */ + int npdi; /* Inbound NP database dip indicator */ + const char *tohost; /* Inbound host of To URI */ + const char *toport; /* Inbound port of To URI */ + char divuser[OSP_SIZE_NORSTR]; /* Inbound user of SIP Diversion header */ + char divhost[OSP_SIZE_NORSTR]; /* Inbound hostport of SIP Diversion header */ + const char *srcnid; /* Inbound source network ID */ + switch_time_t start; /* Call start time */ + const char *cinfo[OSP_MAX_CINFO]; /* Custom info */ +} osp_inbound_t; + +typedef struct osp_destination { + unsigned int timelimit; /* Outbound duration limit */ + char dest[OSP_SIZE_NORSTR]; /* Destination IP address */ + char calling[OSP_SIZE_NORSTR]; /* Outbound calling number, may be translated */ + char called[OSP_SIZE_NORSTR]; /* Outbound called number, may be translated */ + char destnid[OSP_SIZE_NORSTR]; /* Destination network ID */ + char nprn[OSP_SIZE_NORSTR]; /* Outbound NP routing number */ + char npcic[OSP_SIZE_NORSTR]; /* Outbound NP carrier identification code */ + int npdi; /* Outbound NP database dip indicator */ + char opname[OSPC_OPNAME_NUMBER][OSP_SIZE_NORSTR]; /* Outbound Operator names */ + OSPE_DEST_PROTOCOL protocol; /* Destination protocol */ + switch_bool_t supported; /* Supported by FreeRADIUS OSP module */ + switch_bool_t userphone; /* Add "user=phone" parameter */ +} osp_destination_t; + +typedef struct osp_results { + const char *profile; /* Provider name */ + unsigned long long transid; /* Transaction ID */ + switch_time_t start; /* Call start time */ + char called[OSP_SIZE_NORSTR]; /* Original called number */ + const char *srcnid; /* Source network ID */ + int numdest; /* Number of destinations */ + osp_destination_t dests[OSP_MAX_SP]; /* Destinations */ +} osp_results_t; + +typedef struct osp_cookie { + const char *profile; /* Provider name */ + unsigned long long transid; /* Transaction ID */ + switch_time_t start; /* Call start time */ + int destcount; /* Destination count */ + const char *dest; /* Destination IP */ + const char *srcnid; /* Source network ID */ + const char *destnid; /* Destination network ID */ +} osp_cookie_t; + +typedef struct osp_usage { + const char *callid; /* Call-ID */ + const char *calling; /* Calling number */ + char called[OSP_SIZE_NORSTR]; /* Called number */ + const char *srcdev; /* Source device IP */ + OSPE_DEST_PROTOCOL protocol; /* Destination protocol */ + int release; /* Release source */ + switch_call_cause_t cause; /* Termination cause */ + switch_time_t alert; /* Call alert time */ + switch_time_t connect; /* Call answer time */ + switch_time_t end; /* Call end time */ + switch_time_t duration; /* Call duration */ + switch_time_t pdd; /* Post dial delay */ + const char *fcodec; /* Forward codec */ + const char *rcodec; /* Reverse codec */ + int rtpdownoctets; /* RTP downstream bytes */ + int rtpupoctets; /* RTP upstream bytes */ + int rtpdownpackets; /* RTP downstream packets */ + int rtpuppackets; /* RTP upstream packets */ +} osp_usage_t; + +typedef struct osp_threadarg { + OSPTTRANHANDLE handle; /* Transaction handle */ + unsigned long long transid; /* Transaction ID */ + switch_call_cause_t cause; /* Release code */ + time_t start; /* Call start time */ + time_t alert; /* Call alert time */ + time_t connect; /* Call connect time */ + time_t end; /* Call end time */ + int duration; /* Call duration */ + int pdd; /* Post dial delay */ + int release; /* EP that released the call */ +} osp_threadarg_t; + +/* OSP module global settings */ +static osp_settings_t osp_globals; + +/* OSP module providers */ +static osp_provider_t *osp_providers = NULL; + +/* switch_status_t mod_osp_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool) */ +SWITCH_MODULE_LOAD_FUNCTION(mod_osp_load); +/* switch_status_t mod_osp_shutdown(void) */ +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_osp_shutdown); +/* SWITCH_MODULE_DEFINITION(name, load, shutdown, runtime) */ +SWITCH_MODULE_DEFINITION(mod_osp, mod_osp_load, mod_osp_shutdown, NULL); + +/* Macro to prevent NULL string */ +#define osp_filter_null(_str) switch_strlen_zero(_str) ? OSP_DEF_STRING : _str +#define osp_adjust_len(_head, _size, _len) { _len = strlen(_head); _head += _len; _size -= _len; } + +/* + * Find OSP provider by name + * param name OSP provider name + * param provider OSP provider, NULL is allowed + * return SWITCH_STATUS_SUCCESS Successful, SWITCH_STATUS_FALSE Failed + */ +static switch_status_t osp_find_provider( + const char *name, + osp_provider_t **provider) +{ + osp_provider_t *p; + switch_status_t status = SWITCH_STATUS_FALSE; + + if (name) { + if (provider) { + *provider = NULL; + } + + for (p = osp_providers; p; p = p->next) { + if (!strcasecmp(p->name, name)) { + if (provider) { + *provider = p; + } + status = SWITCH_STATUS_SUCCESS; + break; + } + } + } + + return status; +} + +/* + * Load OSP module configuration + * param pool OSP module memory pool + * return SWITCH_STATUS_SUCCESS Successful, SWITCH_STATUS_FALSE Failed, SWITCH_STATUS_MEMERR Memory Error. + */ +static switch_status_t osp_load_settings( + switch_memory_pool_t *pool) +{ + char *cf = "osp.conf"; + switch_xml_t cfg, xml = NULL, param, settings, profile, profiles; + const char *name; + const char *value; + const char *module; + const char *context; + osp_provider_t *provider; + int number; + switch_status_t status = SWITCH_STATUS_SUCCESS; + + if (!(xml = switch_xml_open_cfg(cf, &cfg, NULL))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to open '%s'\n", cf); + status = SWITCH_STATUS_FALSE; + return status; + } + + memset(&osp_globals, 0, sizeof(osp_globals)); + osp_globals.loglevel = SWITCH_LOG_DEBUG; + osp_globals.pool = pool; + osp_globals.protocol = OSPC_DPROT_SIP; + + if ((settings = switch_xml_child(cfg, "settings"))) { + for (param = switch_xml_child(settings, "param"); param; param = param->next) { + name = switch_xml_attr_soft(param, "name"); + value = switch_xml_attr_soft(param, "value"); + module = switch_xml_attr_soft(param, "module"); + context = switch_xml_attr_soft(param, "profile"); + if (switch_strlen_zero(name)) { + continue; + } + if (!strcasecmp(name, "debug-info")) { + if (!switch_strlen_zero(value)) { + osp_globals.debug = switch_true(value); + } + } else if (!strcasecmp(name, "log-level")) { + if (switch_strlen_zero(value)) { + continue; + } else if (!strcasecmp(value, "console")) { + osp_globals.loglevel = SWITCH_LOG_CONSOLE; + } else if (!strcasecmp(value, "alert")) { + osp_globals.loglevel = SWITCH_LOG_ALERT; + } else if (!strcasecmp(value, "crit")) { + osp_globals.loglevel = SWITCH_LOG_CRIT; + } else if (!strcasecmp(value, "error")) { + osp_globals.loglevel = SWITCH_LOG_ERROR; + } else if (!strcasecmp(value, "warning")) { + osp_globals.loglevel = SWITCH_LOG_WARNING; + } else if (!strcasecmp(value, "notice")) { + osp_globals.loglevel = SWITCH_LOG_NOTICE; + } else if (!strcasecmp(value, "info")) { + osp_globals.loglevel = SWITCH_LOG_INFO; + } else if (!strcasecmp(value, "debug")) { + osp_globals.loglevel = SWITCH_LOG_DEBUG; + } + } else if (!strcasecmp(name, "crypto-hardware")) { + if (!switch_strlen_zero(value)) { + osp_globals.hardware = switch_true(value); + } + } else if (!strcasecmp(name, "default-protocol")) { + if (switch_strlen_zero(value)) { + continue; + } else if (!strcasecmp(value, OSP_PROTOCOL_SIP)) { + osp_globals.protocol = OSPC_DPROT_SIP; + } else if (!strcasecmp(value, OSP_PROTOCOL_H323)) { + osp_globals.protocol = OSPC_DPROT_Q931; + } else if (!strcasecmp(value, OSP_PROTOCOL_IAX)) { + osp_globals.protocol = OSPC_DPROT_IAX; + } else if (!strcasecmp(value, OSP_PROTOCOL_SKYPE)) { + osp_globals.protocol = OSPC_DPROT_SKYPE; + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unsupported default protocol '%s'\n", value); + } + } else if (!strcasecmp(name, "sip")) { + if (!switch_strlen_zero(module)) { + if (!(osp_globals.module[OSPC_DPROT_SIP] = switch_core_strdup(osp_globals.pool, module))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to duplicate SIP module name\n"); + status = SWITCH_STATUS_MEMERR; + break; + } + } + if (!switch_strlen_zero(context)) { + if (!(osp_globals.profile[OSPC_DPROT_SIP] = switch_core_strdup(osp_globals.pool, context))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to duplicate SIP profile name\n"); + status = SWITCH_STATUS_MEMERR; + break; + } + } + } else if (!strcasecmp(name, "h323")) { + if (!switch_strlen_zero(module)) { + if (!(osp_globals.module[OSPC_DPROT_Q931] = switch_core_strdup(osp_globals.pool, module))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to duplicate H.323 module name\n"); + status = SWITCH_STATUS_MEMERR; + break; + } + } + if (!switch_strlen_zero(context)) { + if (!(osp_globals.profile[OSPC_DPROT_Q931] = switch_core_strdup(osp_globals.pool, context))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to duplicate H.323 profile name\n"); + status = SWITCH_STATUS_MEMERR; + break; + } + } + } else if (!strcasecmp(name, "iax")) { + if (!switch_strlen_zero(module)) { + if (!(osp_globals.module[OSPC_DPROT_IAX] = switch_core_strdup(osp_globals.pool, module))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to duplicate IAX module name\n"); + status = SWITCH_STATUS_MEMERR; + break; + } + } + if (!switch_strlen_zero(context)) { + if (!(osp_globals.profile[OSPC_DPROT_IAX] = switch_core_strdup(osp_globals.pool, context))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to duplicate IAX profile name\n"); + status = SWITCH_STATUS_MEMERR; + break; + } + } + } else if (!strcasecmp(name, "skype")) { + if (!switch_strlen_zero(module)) { + if (!(osp_globals.module[OSPC_DPROT_SKYPE] = switch_core_strdup(osp_globals.pool, module))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to duplicate Skype module name\n"); + status = SWITCH_STATUS_MEMERR; + break; + } + } + if (!switch_strlen_zero(context)) { + if (!(osp_globals.profile[OSPC_DPROT_SKYPE] = switch_core_strdup(osp_globals.pool, context))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to duplicate Skype profile name\n"); + status = SWITCH_STATUS_MEMERR; + break; + } + } + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unknown parameter '%s'\n", name); + } + } + } + + if (status != SWITCH_STATUS_SUCCESS) { + switch_xml_free(xml); + return status; + } + + if ((profiles = switch_xml_child(cfg, "profiles"))) { + for (profile = switch_xml_child(profiles, "profile"); profile; profile = profile->next) { + name = switch_xml_attr_soft(profile, "name"); + if (switch_strlen_zero(name)) { + name = OSP_DEF_PROFILE; + } + if (osp_find_provider(name, NULL) == SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Ignored duplicate profile '%s'\n", name); + continue; + } + if (!(provider = switch_core_alloc(osp_globals.pool, sizeof(*provider)))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to alloc provider\n"); + status = SWITCH_STATUS_MEMERR; + break; + } + if (!(provider->name = switch_core_strdup(osp_globals.pool, name))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to duplicate provider name\n"); + status = SWITCH_STATUS_MEMERR; + /* "provider" cannot free to pool in FreeSWITCH */ + break; + } + + /* Provider has been set to 0 by switch_core_alloc */ + provider->lifetime = OSP_DEF_LIFETIME; + provider->maxconnect = OSP_DEF_MAXCONN; + provider->persistence = OSP_DEF_PERSIST; + provider->retrydelay = OSP_DEF_RETRYDELAY; + provider->retrylimit = OSP_DEF_RETRYLIMIT; + provider->timeout = OSP_DEF_TIMEOUT; + provider->maxdest = OSP_DEF_MAXDEST; + provider->handle = OSP_INVALID_HANDLE; + + for (param = switch_xml_child(profile, "param"); param; param = param->next) { + name = switch_xml_attr_soft(param, "name"); + value = switch_xml_attr_soft(param, "value"); + if (switch_strlen_zero(name) || switch_strlen_zero(value)) { + continue; + } + if (!strcasecmp(name, "service-point-url")) { + if (provider->spnum < OSP_MAX_SP) { + provider->spurls[provider->spnum] = switch_core_strdup(osp_globals.pool, value); + provider->spnum++; + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Ignored excess service point '%s'\n", value); + } + } else if (!strcasecmp(name, "device-ip")) { + provider->device = switch_core_strdup(osp_globals.pool, value); + } else if (!strcasecmp(name, "ssl-lifetime")) { + provider->lifetime = atoi(value); + } else if (!strcasecmp(name, "http-max-connections")) { + number = atoi(value); + if ((number >= OSP_MIN_MAXCONN) && (number <= OSP_MAX_MAXCONN)) { + provider->maxconnect = number; + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, + "http-max-connections must be between %d and %d\n", OSP_MIN_MAXCONN, OSP_MAX_MAXCONN); + } + } else if (!strcasecmp(name, "http-persistence")) { + provider->persistence = atoi(value); + } else if (!strcasecmp(name, "http-retry-delay")) { + number = atoi(value); + if ((number >= OSP_MIN_RETRYDELAY) && (number <= OSP_MAX_RETRYDELAY)) { + provider->retrydelay = number; + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, + "http-retry-delay must be between %d and %d\n", OSP_MIN_RETRYDELAY, OSP_MAX_RETRYDELAY); + } + } else if (!strcasecmp(name, "http-retry-limit")) { + number = atoi(value); + if ((number >= OSP_MIN_RETRYLIMIT) && (number <= OSP_MAX_RETRYLIMIT)) { + provider->retrylimit = number; + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, + "http-retry-limit must be between %d and %d\n", OSP_MIN_RETRYLIMIT, OSP_MAX_RETRYLIMIT); + } + } else if (!strcasecmp(name, "http-timeout")) { + number = atoi(value); + if ((number >= OSP_MIN_TIMEOUT) && (number <= OSP_MAX_TIMEOUT)) { + provider->timeout = number; + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, + "http-timeout must be between %d and %d\n", OSP_MIN_TIMEOUT, OSP_MAX_TIMEOUT); + } + } else if (!strcasecmp(name, "service-type")) { + if (!strcasecmp(value, "voice")) { + provider->srvtype = OSP_SRV_VOICE; + } else if (!strcasecmp(value, "npquery")) { + provider->srvtype = OSP_SRV_NPQUERY; + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unknown service type '%s'\n", value); + } + } else if (!strcasecmp(name, "max-destinations")) { + number = atoi(value); + if ((number >= OSP_MIN_MAXDEST) && (number <= OSP_MAX_MAXDEST)) { + provider->maxdest = number; + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, + "max-destinations must be between %d and %d\n", OSP_MIN_MAXDEST, OSP_MAX_MAXDEST); + } + } else if (!strcasecmp(name, "user-phone")) { + provider->userphone = switch_true(value); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unknown parameter '%s'\n", name); + } + } + + if (!provider->spnum) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Without service point URI in profile '%s'\n", provider->name); + /* "provider" cannot free to pool in FreeSWITCH */ + continue; + } + + provider->next = osp_providers; + osp_providers = provider; + } + } + + switch_xml_free(xml); + + return status; +} + +/* OSP default certificates */ +const char *B64PKey = "MIIBOgIBAAJBAK8t5l+PUbTC4lvwlNxV5lpl+2dwSZGW46dowTe6y133XyVEwNiiRma2YNk3xKs/TJ3Wl9Wpns2SYEAJsFfSTukCAwEAAQJAPz13vCm2GmZ8Zyp74usTxLCqSJZNyMRLHQWBM0g44Iuy4wE3vpi7Wq+xYuSOH2mu4OddnxswCP4QhaXVQavTAQIhAOBVCKXtppEw9UaOBL4vW0Ed/6EA/1D8hDW6St0h7EXJAiEAx+iRmZKhJD6VT84dtX5ZYNVk3j3dAcIOovpzUj9a0CECIEduTCapmZQ5xqAEsLXuVlxRtQgLTUD4ZxDElPn8x0MhAiBE2HlcND0+qDbvtwJQQOUzDgqg5xk3w8capboVdzAlQQIhAMC+lDL7+gDYkNAft5Mu+NObJmQs4Cr+DkDFsKqoxqrm"; +const char *B64LCert = "MIIBeTCCASMCEHqkOHVRRWr+1COq3CR/xsowDQYJKoZIhvcNAQEEBQAwOzElMCMGA1UEAxMcb3NwdGVzdHNlcnZlci50cmFuc25leHVzLmNvbTESMBAGA1UEChMJT1NQU2VydmVyMB4XDTA1MDYyMzAwMjkxOFoXDTA2MDYyNDAwMjkxOFowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCvLeZfj1G0wuJb8JTcVeZaZftncEmRluOnaME3ustd918lRMDYokZmtmDZN8SrP0yd1pfVqZ7NkmBACbBX0k7pAgMBAAEwDQYJKoZIhvcNAQEEBQADQQDnV8QNFVVJx/+7IselU0wsepqMurivXZzuxOmTEmTVDzCJx1xhA8jd3vGAj7XDIYiPub1PV23eY5a2ARJuw5w9"; +const char *B64CACert = "MIIBYDCCAQoCAQEwDQYJKoZIhvcNAQEEBQAwOzElMCMGA1UEAxMcb3NwdGVzdHNlcnZlci50cmFuc25leHVzLmNvbTESMBAGA1UEChMJT1NQU2VydmVyMB4XDTAyMDIwNDE4MjU1MloXDTEyMDIwMzE4MjU1MlowOzElMCMGA1UEAxMcb3NwdGVzdHNlcnZlci50cmFuc25leHVzLmNvbTESMBAGA1UEChMJT1NQU2VydmVyMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPGeGwV41EIhX0jEDFLRXQhDEr50OUQPq+f55VwQd0TQNts06BP29+UiNdRW3c3IRHdZcJdC1Cg68ME9cgeq0h8CAwEAATANBgkqhkiG9w0BAQQFAANBAGkzBSj1EnnmUxbaiG1N4xjIuLAWydun7o3bFk2tV8dBIhnuh445obYyk1EnQ27kI7eACCILBZqi2MHDOIMnoN0="; + +/* + * Init OSP client end + * return + */ +static void osp_init_osptk(void) +{ + osp_provider_t *provider; + OSPTPRIVATEKEY privatekey; + unsigned char privatekeydata[OSP_SIZE_KEYSTR]; + OSPT_CERT localcert; + unsigned char localcertdata[OSP_SIZE_KEYSTR]; + const OSPT_CERT *pcacert; + OSPT_CERT cacert; + unsigned char cacertdata[OSP_SIZE_KEYSTR]; + int error; + + if (osp_globals.hardware) { + if ((error = OSPPInit(OSPC_TRUE)) != OSPC_ERR_NO_ERROR) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unable to enable crypto hardware, error '%d'\n", error); + osp_globals.hardware = SWITCH_FALSE; + OSPPInit(OSPC_FALSE); + } + } else { + OSPPInit(OSPC_FALSE); + } + + for (provider = osp_providers; provider; provider = provider->next) { + privatekey.PrivateKeyData = privatekeydata; + privatekey.PrivateKeyLength = sizeof(privatekeydata); + + localcert.CertData = localcertdata; + localcert.CertDataLength = sizeof(localcertdata); + + pcacert = &cacert; + cacert.CertData = cacertdata; + cacert.CertDataLength = sizeof(cacertdata); + + if ((error = OSPPBase64Decode(B64PKey, strlen(B64PKey), privatekey.PrivateKeyData, &privatekey.PrivateKeyLength)) != OSPC_ERR_NO_ERROR) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unable to decode private key, error '%d'\n", error); + } else if ((error = OSPPBase64Decode(B64LCert, strlen(B64LCert), localcert.CertData, &localcert.CertDataLength)) != OSPC_ERR_NO_ERROR) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unable to decode local cert, error '%d'\n", error); + } else if ((error = OSPPBase64Decode(B64CACert, strlen(B64CACert), cacert.CertData, &cacert.CertDataLength)) != OSPC_ERR_NO_ERROR) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unable to decode cacert, error '%d'\n", error); + } + + if (error == OSPC_ERR_NO_ERROR) { + error = OSPPProviderNew( + provider->spnum, /* Number of service points */ + provider->spurls, /* Service point URLs */ + NULL, /* Weights */ + OSP_AUDIT_URL, /* Audit URL */ + &privatekey, /* Provate key */ + &localcert, /* Local cert */ + 1, /* Number of cacerts */ + &pcacert, /* cacerts */ + OSP_LOCAL_VALID, /* Validating method */ + provider->lifetime, /* SS lifetime */ + provider->maxconnect, /* HTTP max connections */ + provider->persistence, /* HTTP persistence */ + provider->retrydelay, /* HTTP retry delay, in seconds */ + provider->retrylimit, /* HTTP retry times */ + provider->timeout, /* HTTP timeout */ + OSP_CUSTOMER_ID, /* Customer ID */ + OSP_DEVICE_ID, /* Device ID */ + &provider->handle); /* Provider handle */ + if (error != OSPC_ERR_NO_ERROR) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unable to create provider %s, error '%d'\n", provider->name, error); + provider->handle = OSP_INVALID_HANDLE; + } + } + } +} + +/* + * Get protocol name + * param protocol Supported protocol + * return protocol name + */ +static const char *osp_get_protocol( + OSPE_DEST_PROTOCOL protocol) +{ + const char *name; + + switch (protocol) { + case OSPC_DPROT_UNKNOWN: + name = OSP_PROTOCOL_UNKNO; + break; + case OSPC_DPROT_UNDEFINED: + name = OSP_PROTOCOL_UNDEF; + break; + case OSPC_DPROT_SIP: + name = OSP_PROTOCOL_SIP; + break; + case OSPC_DPROT_Q931: + name = OSP_PROTOCOL_H323; + break; + case OSPC_DPROT_IAX: + name = OSP_PROTOCOL_IAX; + break; + case OSPC_DPROT_SKYPE: + name = OSP_PROTOCOL_SKYPE; + break; + case OSPC_DPROT_LRQ: + case OSPC_DPROT_T37: + case OSPC_DPROT_T38: + case OSPC_DPROT_SMPP: + case OSPC_DPROT_XMPP: + case OSPC_DPROT_SMS: + default: + name = OSP_PROTOCOL_UNSUP; + break; + } + + return name; +} + +/* + * Macro expands to: + * static switch_status_t osp_api_function(_In_opt_z_ const char *cmd, _In_opt_ switch_core_session_t *session, _In_ switch_stream_handle_t *stream) + */ +SWITCH_STANDARD_API(osp_api_function) +{ + int i, argc = 0; + char *argv[2] = { 0 }; + char *params = NULL; + char *param = NULL; + osp_provider_t *provider; + char *loglevel; + + if (session) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "This function cannot be called from the dialplan.\n"); + return SWITCH_STATUS_FALSE; + } + + if (switch_strlen_zero(cmd)) { + stream->write_function(stream, "Usage: osp status\n"); + return SWITCH_STATUS_SUCCESS; + } + + if (!(params = switch_safe_strdup(cmd))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to duplicate parameters\n"); + return SWITCH_STATUS_MEMERR; + } + + if ((argc = switch_separate_string(params, ' ', argv, (sizeof(argv) / sizeof(argv[0]))))) { + param = argv[0]; + if (!strcasecmp(param, "status")) { + stream->write_function(stream, "=============== OSP Module Settings & Status ===============\n"); + stream->write_function(stream, " debug-info: %s\n", osp_globals.debug ? "enabled" : "disabled"); + switch (osp_globals.loglevel) { + case SWITCH_LOG_CONSOLE: + loglevel = "console"; + break; + case SWITCH_LOG_ALERT: + loglevel = "alert"; + break; + case SWITCH_LOG_CRIT: + loglevel = "crit"; + break; + case SWITCH_LOG_ERROR: + loglevel = "error"; + break; + case SWITCH_LOG_WARNING: + loglevel = "warning"; + break; + case SWITCH_LOG_NOTICE: + loglevel = "notice"; + break; + case SWITCH_LOG_INFO: + loglevel = "info"; + break; + case SWITCH_LOG_DEBUG: + default: + loglevel = "debug"; + break; + } + stream->write_function(stream, " log-level: %s\n", loglevel); + stream->write_function(stream, " crypto-hardware: %s\n", osp_globals.hardware ? "enabled" : "disabled"); + if (switch_strlen_zero(osp_globals.module[OSPC_DPROT_SIP]) || switch_strlen_zero(osp_globals.profile[OSPC_DPROT_SIP])) { + stream->write_function(stream, " sip: unsupported\n"); + } else { + stream->write_function(stream, " sip: %s/%s\n", + osp_globals.module[OSPC_DPROT_SIP], osp_globals.profile[OSPC_DPROT_SIP]); + } + if (switch_strlen_zero(osp_globals.module[OSPC_DPROT_Q931]) || switch_strlen_zero(osp_globals.profile[OSPC_DPROT_Q931])) { + stream->write_function(stream, " h323: unsupported\n"); + } else { + stream->write_function(stream, " h323: %s/%s\n", + osp_globals.module[OSPC_DPROT_Q931], osp_globals.profile[OSPC_DPROT_Q931]); + } + if (switch_strlen_zero(osp_globals.module[OSPC_DPROT_IAX]) || switch_strlen_zero(osp_globals.profile[OSPC_DPROT_IAX])) { + stream->write_function(stream, " iax: unsupported\n"); + } else { + stream->write_function(stream, " iax: %s/%s\n", + osp_globals.module[OSPC_DPROT_IAX], osp_globals.profile[OSPC_DPROT_IAX]); + } + if (switch_strlen_zero(osp_globals.module[OSPC_DPROT_SKYPE]) || switch_strlen_zero(osp_globals.profile[OSPC_DPROT_SKYPE])) { + stream->write_function(stream, " skype: unsupported\n"); + } else { + stream->write_function(stream, " skype: %s/%s\n", + osp_globals.module[OSPC_DPROT_SKYPE], osp_globals.profile[OSPC_DPROT_SKYPE]); + } + stream->write_function(stream, " default-protocol: %s\n", osp_get_protocol(osp_globals.protocol)); + stream->write_function(stream, "============== OSP Profiles Settings & Status ==============\n"); + for (provider = osp_providers; provider; provider = provider->next) { + stream->write_function(stream, "Profile: %s\n", provider->name); + for (i = 0; i < provider->spnum; i++) { + stream->write_function(stream, " service-point-url: %s\n", provider->spurls[i]); + } + stream->write_function(stream, " device-ip: %s\n", provider->device); + stream->write_function(stream, " ssl-lifetime: %d\n", provider->lifetime); + stream->write_function(stream, " http-max-connections: %d\n", provider->maxconnect); + stream->write_function(stream, " http-persistence: %d\n", provider->persistence); + stream->write_function(stream, " http-retry-dalay: %d\n", provider->retrydelay); + stream->write_function(stream, " http-retry-limit: %d\n", provider->retrylimit); + stream->write_function(stream, " http-timeout: %d\n", provider->timeout); + switch (provider->srvtype) { + case OSP_SRV_NPQUERY: + stream->write_function(stream, " service-type: npquery\n"); + break; + case OSP_SRV_VOICE: + default: + stream->write_function(stream, " service-type: voice\n"); + break; + } + stream->write_function(stream, " max-destinations: %d\n", provider->maxdest); + stream->write_function(stream, " user-phone: %s\n", provider->userphone ? "enabled" : "disabled"); + stream->write_function(stream, " status: %s\n", provider->handle != OSP_INVALID_HANDLE ? "enabled" : "disabled"); + } + } else { + stream->write_function(stream, "Invalid Syntax!\n"); + } + } else { + stream->write_function(stream, "Invalid Input!\n"); + } + + switch_safe_free(params); + + return SWITCH_STATUS_SUCCESS; +} + +/* + * Parse URI userinfo + * param user URI userinfo + * param user URI user + * param usersize Size of user buffer + * param inbound Inbound info + * return + */ +static void osp_parse_userinfo( + const char *userinfo, + char *user, + switch_size_t usersize, + osp_inbound_t *inbound) +{ + char buffer[OSP_SIZE_NORSTR]; + char *item; + char *tmp; + + if (switch_strlen_zero(userinfo)) { + if (user && usersize) { + user[0] = '\0'; + } + /* It is not necessary to clean inbound */ + return; + } + + switch_copy_string(buffer, userinfo, sizeof(buffer)); + + item = strtok_r(buffer, OSP_USER_DELIM, &tmp); + + if (user && usersize) { + switch_copy_string(user, item, usersize); + } + + if (inbound) { + switch_copy_string(inbound->called, item, sizeof(inbound->called)); + + for (item = strtok_r(NULL, OSP_USER_DELIM, &tmp); item; item = strtok_r(NULL, OSP_USER_DELIM, &tmp)) { + if (!strncasecmp(item, "rn=", 3)) { + switch_copy_string(inbound->nprn, item + 3, sizeof(inbound->nprn)); + } else if (!strncasecmp(item, "cic=", 4)) { + switch_copy_string(inbound->npcic, item + 4, sizeof(inbound->npcic)); + } else if (!strcasecmp(item, "npdi")) { + inbound->npdi = 1; + } + } + } +} + +/* + * Parse SIP Diversion + * param diversion SIP Diversion header + * param inbound Inbound info + * return + */ +static void osp_parse_diversion( + const char *diversion, + osp_inbound_t *inbound) +{ + char buffer[OSP_SIZE_NORSTR]; + char userinfo[OSP_SIZE_NORSTR]; + char *head; + char *tmp; + + if (switch_strlen_zero(diversion)) { + /* It is not necessary to clean inbound */ + return; + } + + switch_copy_string(buffer, diversion, sizeof(buffer)); + + if ((head = strstr(buffer, "sip:"))) { + head += 4; + if ((tmp = strchr(head, OSP_URI_DELIM))) { + *tmp = '\0'; + switch_copy_string(userinfo, head, sizeof(userinfo)); + osp_parse_userinfo(userinfo, inbound->divuser, sizeof(inbound->divuser), NULL); + + head = tmp + 1; + tmp = strtok(head, OSP_HOST_DELIM); + switch_copy_string(inbound->divhost, tmp, sizeof(inbound->divhost)); + } + } +} + +/* + * Log AuthReq parameters + * param provider OSP provider + * param inbound Inbound info + * return + */ +static void osp_log_authreq( + osp_provider_t *provider, + osp_inbound_t *inbound) +{ + char *srvtype; + char term[OSP_SIZE_NORSTR]; + int total; + + if (osp_globals.debug) { + if (provider->srvtype == OSP_SRV_NPQUERY) { + srvtype = "npquery"; + if (switch_strlen_zero(inbound->tohost)) { + switch_copy_string(term, provider->device, sizeof(term)); + } else { + if (switch_strlen_zero(inbound->toport)) { + switch_copy_string(term, inbound->tohost, sizeof(term)); + } else { + switch_snprintf(term, sizeof(term), "%s:%s", inbound->tohost, inbound->toport); + } + } + total = 1; + } else { + srvtype = "voice"; + term[0] = '\0'; + total = provider->maxdest; + } + + switch_log_printf(SWITCH_CHANNEL_LOG, osp_globals.loglevel, + "AuthReq: " + "srvtype = '%s' " + "source = '%s' " + "srcdev = '%s' " + "calling = '%s' " + "called = '%s' " + "lnp = '%s/%s/%d' " + "prefer = '%s' " + "div = '%s/%s' " + "srcnid = '%s' " + "cinfo = '%s/%s/%s/%s/%s/%s/%s/%s' " + "maxcount = '%d'\n", + srvtype, + provider->device, + inbound->srcdev, + inbound->calling, + inbound->called, + inbound->nprn, inbound->npcic, inbound->npdi, + term, + inbound->divuser, inbound->divhost, + osp_filter_null(inbound->srcnid), + osp_filter_null(inbound->cinfo[0]), osp_filter_null(inbound->cinfo[1]), + osp_filter_null(inbound->cinfo[2]), osp_filter_null(inbound->cinfo[3]), + osp_filter_null(inbound->cinfo[4]), osp_filter_null(inbound->cinfo[5]), + osp_filter_null(inbound->cinfo[6]), osp_filter_null(inbound->cinfo[7]), + total); + } +} + +/* + * Get inbound info + * param channel Inbound channel + * param inbound Inbound info + * return + */ +static void osp_get_inbound( + switch_channel_t *channel, + osp_inbound_t *inbound) +{ + switch_caller_profile_t *caller; + switch_channel_timetable_t *times; + const char *tmp; + int i; + char name[OSP_SIZE_NORSTR]; + + memset(inbound, 0, sizeof(*inbound)); + + caller = switch_channel_get_caller_profile(channel); + inbound->srcdev = caller->network_addr; + inbound->calling = caller->caller_id_number; + osp_parse_userinfo(caller->destination_number, NULL, 0, inbound); + + inbound->tohost = switch_channel_get_variable(channel, OSP_FS_TOHOST); + inbound->toport = switch_channel_get_variable(channel, OSP_FS_TOPORT); + + if ((tmp = switch_channel_get_variable(channel, OSP_FS_DIVERSION))) { + osp_parse_diversion(tmp, inbound); + } + + inbound->srcnid = switch_channel_get_variable(channel, OSP_VAR_SRCNID); + + times = switch_channel_get_timetable(channel); + inbound->start = times->created; + + for (i = 0; i < OSP_MAX_CINFO; i++) { + switch_snprintf(name, sizeof(name), "%s%d", OSP_VAR_CUSTOMINFO, i + 1); + inbound->cinfo[i] = switch_channel_get_variable(channel, name); + } +} + +/* + * Convert "address:port" to "[x.x.x.x]:port" or "hostname:port" format + * param src Source address string + * param dest Destination address string + * param destsize Size of dest buffer + * return + */ +static void osp_convert_inout( + const char *src, + char *dest, + int destsize) +{ + struct in_addr inp; + char buffer[OSP_SIZE_NORSTR]; + char *port; + + if (dest && (destsize > 0)) { + if (!switch_strlen_zero(src)) { + switch_copy_string(buffer, src, sizeof(buffer)); + + if((port = strchr(buffer, ':'))) { + *port = '\0'; + port++; + } + + if (inet_pton(AF_INET, buffer, &inp) == 1) { + if (port) { + switch_snprintf(dest, destsize, "[%s]:%s", buffer, port); + } else { + switch_snprintf(dest, destsize, "[%s]", buffer); + } + dest[destsize - 1] = '\0'; + } else { + switch_copy_string(dest, src, destsize); + } + } else { + *dest = '\0'; + } + } +} + +/* + * Convert "[x.x.x.x]:port" or "hostname:prot" to "address:port" format + * param src Source address string + * param dest Destination address string + * param destsize Size of dest buffer + * return + */ +static void osp_convert_outin( + const char *src, + char *dest, + int destsize) +{ + char buffer[OSP_SIZE_NORSTR]; + char *end; + char *port; + + if (dest && (destsize > 0)) { + if (!switch_strlen_zero(src)) { + switch_copy_string(buffer, src, sizeof(buffer)); + + if (buffer[0] == '[') { + if((port = strchr(buffer + 1, ':'))) { + *port = '\0'; + port++; + } + + if ((end = strchr(buffer + 1, ']'))) { + *end = '\0'; + } + + if (port) { + switch_snprintf(dest, destsize, "%s:%s", buffer + 1, port); + dest[destsize - 1] = '\0'; + } else { + switch_copy_string(dest, buffer + 1, destsize); + } + } else { + switch_copy_string(dest, src, destsize); + } + } else { + *dest = '\0'; + } + } +} + +/* + * Check destination + * param handle Transaction handle + * param dest Destination + * return SWITCH_STATUS_SUCCESS Successful, SWITCH_STATUS_FALSE Failed + */ +static switch_status_t osp_check_destination( + OSPTTRANHANDLE handle, + osp_destination_t *dest) +{ + OSPE_DEST_OSPENABLED enabled; + OSPE_DEST_PROTOCOL protocol; + OSPE_OPERATOR_NAME type; + int error; + switch_status_t status = SWITCH_STATUS_FALSE; + + if ((handle == OSP_INVALID_HANDLE) || !dest) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid parameters\n"); + return status; + } + + dest->supported = SWITCH_FALSE; + + if ((error = OSPPTransactionIsDestOSPEnabled(handle, &enabled)) != OSPC_ERR_NO_ERROR) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to get destination OSP version, error '%d'\n", error); + return status; + } + + if ((error = OSPPTransactionGetDestProtocol(handle, &protocol)) != OSPC_ERR_NO_ERROR) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to get destination protocol, error '%d'\n", error); + return status; + } + + switch(protocol) { + case OSPC_DPROT_UNDEFINED: + case OSPC_DPROT_UNKNOWN: + protocol = osp_globals.protocol; + case OSPC_DPROT_SIP: + case OSPC_DPROT_Q931: + case OSPC_DPROT_IAX: + case OSPC_DPROT_SKYPE: + dest->protocol = protocol; + if (!switch_strlen_zero(osp_globals.module[protocol]) && !switch_strlen_zero(osp_globals.profile[protocol])) { + dest->supported = SWITCH_TRUE; + status = SWITCH_STATUS_SUCCESS; + } + break; + case OSPC_DPROT_LRQ: + case OSPC_DPROT_T37: + case OSPC_DPROT_T38: + case OSPC_DPROT_SMPP: + case OSPC_DPROT_XMPP: + default: + dest->protocol = protocol; + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unsupported protocol '%d'\n", protocol); + break; + } + + if ((error = OSPPTransactionGetDestinationNetworkId(handle, sizeof(dest->destnid), dest->destnid)) != OSPC_ERR_NO_ERROR) { + dest->destnid[0] = '\0'; + } + + error = OSPPTransactionGetNumberPortabilityParameters(handle, + sizeof(dest->nprn), + dest->nprn, + sizeof(dest->npcic), + dest->npcic, + &dest->npdi); + if (error != OSPC_ERR_NO_ERROR) { + dest->nprn[0] = '\0'; + dest->npcic[0] = '\0'; + dest->npdi = 0; + } + + for (type = OSPC_OPNAME_START; type < OSPC_OPNAME_NUMBER; type++) { + if ((error = OSPPTransactionGetOperatorName(handle, type, sizeof(dest->opname[type]), dest->opname[type])) != OSPC_ERR_NO_ERROR) { + dest->opname[type][0] = '\0'; + } + } + + return status; +} + +/* + * Log AuthRsp parameters + * param results Routing info + * return + */ +static void osp_log_authrsp( + osp_results_t *results) +{ + int i; + + if (osp_globals.debug) { + for (i = 0; i < results->numdest; i++) { + switch_log_printf(SWITCH_CHANNEL_LOG, osp_globals.loglevel, + "AuthRsp: " + "transid = '%llu' " + "destcount = '%d' " + "timelimit = '%u' " + "destination = '%s' " + "calling = '%s' " + "called = '%s' " + "destnid = '%s' " + "lnp = '%s/%s/%d' " + "protocol = '%s' " + "supported = '%d'\n", + results->transid, + i + 1, + results->dests[i].timelimit, + results->dests[i].dest, + results->dests[i].calling, + results->dests[i].called, + results->dests[i].destnid, + results->dests[i].nprn, results->dests[i].npcic, results->dests[i].npdi, + osp_get_protocol(results->dests[i].protocol), + results->dests[i].supported); + } + } +} + +/* + * Do auth/routing request + * param provider OSP provider + * param handle Transaction handle + * param source Call originator info + * param results Routing info + * return SWITCH_STATUS_SUCCESS Successful, SWITCH_STATUS_FALSE Failed + */ +static switch_status_t osp_do_request( + osp_provider_t *provider, + OSPTTRANHANDLE handle, + osp_inbound_t *inbound, + osp_results_t *results) +{ + OSPTTRANS *context; + osp_destination_t *dest; + char tmp[OSP_SIZE_NORSTR]; + char src[OSP_SIZE_NORSTR]; + char dev[OSP_SIZE_NORSTR]; + char term[OSP_SIZE_NORSTR]; + const char *preferred[2] = { NULL }; + unsigned int callidlen = 0, tokenlen = 0, total; + int count, error; + switch_status_t status = SWITCH_STATUS_FALSE; + + osp_log_authreq(provider, inbound); + + OSPPTransactionSetNumberPortability(handle, inbound->nprn, inbound->npcic, inbound->npdi); + + osp_convert_inout(inbound->divhost, tmp, sizeof(tmp)); + OSPPTransactionSetDiversion(handle, inbound->divuser, tmp); + + OSPPTransactionSetNetworkIds(handle, inbound->srcnid, NULL); + + for (count = 0; count < OSP_MAX_CINFO; count++) { + if (!switch_strlen_zero(inbound->cinfo[count])) { + OSPPTransactionSetCustomInfo(handle, count, inbound->cinfo[count]); + } + } + + osp_convert_inout(provider->device, src, sizeof(src)); + osp_convert_inout(inbound->srcdev, dev, sizeof(dev)); + if (provider->srvtype == OSP_SRV_NPQUERY) { + OSPPTransactionSetServiceType(handle, OSPC_SERVICE_NPQUERY); + + if (switch_strlen_zero(inbound->tohost)) { + switch_copy_string(term, src, sizeof(term)); + } else { + if (switch_strlen_zero(inbound->toport)) { + switch_copy_string(tmp, inbound->tohost, sizeof(tmp)); + } else { + switch_snprintf(tmp, sizeof(tmp), "%s:%s", inbound->tohost, inbound->toport); + } + osp_convert_inout(tmp, term, sizeof(term)); + } + preferred[0] = term; + + total = 1; + } else { + OSPPTransactionSetServiceType(handle, OSPC_SERVICE_VOICE); + + total = provider->maxdest; + } + + error = OSPPTransactionRequestAuthorisation( + handle, /* Transaction handle */ + src, /* Source */ + dev, /* Source device */ + inbound->calling, /* Calling */ + OSPC_NFORMAT_E164, /* Calling format */ + inbound->called, /* Called */ + OSPC_NFORMAT_E164, /* Called format */ + NULL, /* User */ + 0, /* Number of callids */ + NULL, /* Callids */ + preferred, /* Preferred destinations */ + &total, /* Destination number */ + NULL, /* Log buffer size */ + NULL); /* Log buffer */ + if (error != OSPC_ERR_NO_ERROR) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unable to request routing, error '%d'\n", error); + results->numdest = 0; + return status; + } else if (!total) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Without destination\n"); + results->numdest = 0; + return status; + } + + context = OSPPTransactionGetContext(handle, &error); + if (error == OSPC_ERR_NO_ERROR) { + results->transid = context->TransactionID; + } else { + results->transid = 0; + } + + switch_copy_string(results->called, inbound->called, sizeof(results->called)); + results->srcnid = inbound->srcnid; + results->start = inbound->start; + + count = 0; + dest = &results->dests[count]; + error = OSPPTransactionGetFirstDestination( + handle, /* Transaction handle */ + 0, /* Timestamp buffer size */ + NULL, /* Valid after */ + NULL, /* Valid until */ + &dest->timelimit, /* Call duration limit */ + &callidlen, /* Callid buffer size */ + NULL, /* Callid buffer */ + sizeof(dest->called), /* Called buffer size */ + dest->called, /* Called buffer */ + sizeof(dest->calling), /* Calling buffer size */ + dest->calling, /* Calling buffer */ + sizeof(term), /* Destination buffer size */ + term, /* Destination buffer */ + 0, /* Destination device buffer size */ + NULL, /* Destination device buffer */ + &tokenlen, /* Token buffer length */ + NULL); /* Token buffer */ + if (error != OSPC_ERR_NO_ERROR) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to get first destination, error '%d'\n", error); + return status; + } + + osp_convert_outin(term, dest->dest, sizeof(dest->dest)); + osp_check_destination(handle, dest); + dest->userphone = provider->userphone; + + for (count = 1; count < total; count++) { + dest = &results->dests[count]; + error = OSPPTransactionGetNextDestination( + handle, /* Transsaction handle */ + OSPC_FAIL_NONE, /* Failure reason */ + 0, /* Timestamp buffer size */ + NULL, /* Valid after */ + NULL, /* Valid until */ + &dest->timelimit, /* Call duration limit */ + &callidlen, /* Callid buffer size */ + NULL, /* Callid buffer */ + sizeof(dest->called), /* Called buffer size */ + dest->called, /* Called buffer */ + sizeof(dest->calling), /* Calling buffer size */ + dest->calling, /* Calling buffer */ + sizeof(term), /* Destination buffer size */ + term, /* Destination buffer */ + 0, /* Destination device buffer size */ + NULL, /* Destination device buffer */ + &tokenlen, /* Token buffer length */ + NULL); /* Token buffer */ + if (error == OSPC_ERR_NO_ERROR) { + osp_convert_outin(term, dest->dest, sizeof(dest->dest)); + osp_check_destination(handle, dest); + dest->userphone = provider->userphone; + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to get destination, error '%d'\n", error); + break; + } + } + if (count == total) { + results->numdest = total; + osp_log_authrsp(results); + status = SWITCH_STATUS_SUCCESS; + } + + return status; +} + +/* + * Request auth/routing info + * param channel Originator channel + * param profile Provider name + * param results Routing info + * return SWITCH_STATUS_SUCCESS Successful, SWITCH_STATUS_FALSE Failed + */ +static switch_status_t osp_request_routing( + switch_channel_t *channel, + const char *profile, + osp_results_t *results) +{ + osp_provider_t *provider; + OSPTTRANHANDLE handle; + osp_inbound_t inbound; + int error; + switch_status_t status = SWITCH_STATUS_FALSE; + + if (osp_find_provider(profile, &provider) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to find provider '%s'\n", profile); + return status; + } + + if (provider->handle == OSP_INVALID_HANDLE) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Disabled provider '%s'\n", profile); + return status; + } + + results->profile = profile; + + if ((error = OSPPTransactionNew(provider->handle, &handle)) != OSPC_ERR_NO_ERROR) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create transaction handle, error '%d'\n", error); + return status; + } + + osp_get_inbound(channel, &inbound); + + status = osp_do_request(provider, handle, &inbound, results); + + OSPPTransactionDelete(handle); + + return status; +} + +/* + * Build parameter string for all channels + * param results Routing results + * param buffer Buffer + * param bufsize Buffer size + * return + */ +static void osp_build_allparam( + osp_results_t *results, + char *buffer, + switch_size_t bufsize) +{ + char *head = buffer; + switch_size_t len, size = bufsize; + + if (results && head && size) { + switch_snprintf(head, size, + "{%s=%s,%s=%llu,%s=%llu", + OSP_VAR_PROFILE, results->profile, + OSP_VAR_TRANSID, results->transid, + OSP_VAR_START, results->start); + osp_adjust_len(head, size, len); + + if (!switch_strlen_zero(results->srcnid)) { + switch_snprintf(head, size, ",%s=%s", OSP_VAR_SRCNID, results->srcnid); + } + osp_adjust_len(head, size, len); + + switch_snprintf(head, size, "}"); + osp_adjust_len(head, size, len); + } +} + +/* + * Build parameter string for each channel + * param count Destination count + * param dest Destination + * param buffer Buffer + * param bufsize Buffer size + * return + */ +static void osp_build_eachparam( + int count, + osp_destination_t *dest, + char *buffer, + switch_size_t bufsize) +{ + char *head = buffer; + switch_size_t len, size = bufsize; + + if ((count > 0) && dest && head && size) { + switch_snprintf(buffer, bufsize, + "[%s=%d,%s=%s,%s=%s", + OSP_VAR_DESTCOUNT, count, + OSP_VAR_DESTIP, dest->dest, + OSP_FS_OUTCALLING, dest->calling); + osp_adjust_len(head, size, len); + + if (!switch_strlen_zero_buf(dest->destnid)) { + switch_snprintf(head, size, ",%s=%s", OSP_VAR_DESTNID, dest->destnid); + } + osp_adjust_len(head, size, len); + + switch_snprintf(head, size, "]"); + osp_adjust_len(head, size, len); + } +} + +/* + * Build endpoint string + * param dest Destination + * param buffer Buffer + * param bufsize Buffer size + * return + */ +static void osp_build_endpoint( + osp_destination_t *dest, + char *buffer, + switch_size_t bufsize) +{ + char *head = buffer; + switch_size_t len, size = bufsize; + + if (head && size) { + switch (dest->protocol) { + case OSPC_DPROT_SIP: + switch_snprintf(head, size, "%s/%s/%s", osp_globals.module[OSPC_DPROT_SIP], osp_globals.profile[OSPC_DPROT_SIP], dest->called); + osp_adjust_len(head, size, len); + + if (!switch_strlen_zero_buf(dest->nprn)) { + switch_snprintf(head, size, ";rn=%s", dest->nprn); + osp_adjust_len(head, size, len); + } + if (!switch_strlen_zero_buf(dest->npcic)) { + switch_snprintf(head, size, ";cic=%s", dest->npcic); + osp_adjust_len(head, size, len); + } + if (dest->npdi) { + switch_snprintf(head, size, ";npdi"); + osp_adjust_len(head, size, len); + } + + switch_snprintf(head, size, "@%s", dest->dest); + osp_adjust_len(head, size, len); + + if (dest->userphone) { + switch_snprintf(head, size, ";user=phone"); + osp_adjust_len(head, size, len); + } + break; + case OSPC_DPROT_Q931: + switch_snprintf(head, size, "%s/%s/%s@%s", osp_globals.module[OSPC_DPROT_Q931], osp_globals.profile[OSPC_DPROT_Q931], dest->called, dest->dest); + osp_adjust_len(head, size, len); + break; + case OSPC_DPROT_IAX: + switch_snprintf(head, size, "%s/%s/%s/%s", osp_globals.module[OSPC_DPROT_Q931], osp_globals.profile[OSPC_DPROT_Q931], dest->dest, dest->called); + osp_adjust_len(head, size, len); + break; + case OSPC_DPROT_SKYPE: + switch_snprintf(head, size, "%s/%s/%s", osp_globals.module[OSPC_DPROT_Q931], osp_globals.profile[OSPC_DPROT_Q931], dest->called); + osp_adjust_len(head, size, len); + break; + default: + buffer[0] = '\0'; + break; + } + } +} + +/* + * Create route string + * param channel Originator channel + * param numdest Numer of destinations + * param destlist Destinations + * return + */ +static void osp_create_route( + switch_channel_t *channel, + osp_results_t *results) +{ + osp_destination_t *dest; + char name[OSP_SIZE_NORSTR]; + char value[OSP_SIZE_NORSTR]; + char eachparam[OSP_SIZE_NORSTR]; + char endpoint[OSP_SIZE_NORSTR]; + char buffer[OSP_SIZE_ROUSTR]; + int i, len, count, size = sizeof(buffer); + char *head = buffer; + switch_event_header_t *hi; + char *var; + + /* Cleanup OSP varibales in originator */ + if ((hi = switch_channel_variable_first(channel))) { + for (; hi; hi = hi->next) { + var = hi->name; + if (var && !strncmp(var, "osp_", 4)) { + switch_channel_set_variable(channel, var, NULL); + } + } + switch_channel_variable_last(channel); + } + + osp_build_allparam(results, head, size); + osp_adjust_len(head, size, len); + + for (count = 0, i = 0; i < results->numdest; i++) { + dest = &results->dests[i]; + if (dest->supported) { + count++; + osp_build_eachparam(i + 1, dest, eachparam, sizeof(eachparam)); + osp_build_endpoint(dest, endpoint, sizeof(endpoint)); + switch_snprintf(name, sizeof(name), "%s%d", OSP_VAR_ROUTEPRE, count); + switch_snprintf(value, sizeof(value), "%s%s", eachparam, endpoint); + switch_channel_set_variable_var_check(channel, name, value, SWITCH_FALSE); + switch_snprintf(head, size, "%s|", value); + osp_adjust_len(head, size, len); + } + } + + switch_snprintf(value, sizeof(value), "%d", count); + switch_channel_set_variable_var_check(channel, OSP_VAR_ROUTECOUNT, value, SWITCH_FALSE); + + if (count) { + *(buffer + strlen(buffer) - 1) = '\0'; + } else { + buffer[0] = '\0'; + } + switch_channel_set_variable_var_check(channel, OSP_VAR_AUTOROUTE, buffer, SWITCH_FALSE); +} + +/* + * Macro expands to: + * static void osp_app_function(switch_core_session_t *session, const char *data) + */ +SWITCH_STANDARD_APP(osp_app_function) +{ + int argc = 0; + char *argv[2] = { 0 }; + char *params = NULL; + char *profile = NULL; + switch_channel_t *channel; + osp_results_t results; + switch_status_t retval; + + if (osp_globals.shutdown) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "OSP application inavailable\n"); + return; + } + + /* Make sure there is a valid channel when starting the OSP application */ + if (!(channel = switch_core_session_get_channel(session))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to find origiantor channel\n"); + return; + } + + if (!(params = switch_core_session_strdup(session, data))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to alloc parameters\n"); + return; + } + + if ((argc = switch_separate_string(params, ' ', argv, (sizeof(argv) / sizeof(argv[0]))))) { + profile = argv[0]; + } else { + profile = OSP_DEF_PROFILE; + } + + retval = osp_request_routing(channel, profile, &results); + if (retval == SWITCH_STATUS_SUCCESS) { + osp_create_route(channel, &results); + } +} + +/* + * Add application + * param session + * param channel Originator channel + * param extenstion + * param results OSP routing info + * return + */ +static void osp_add_application( + switch_core_session_t *session, + switch_channel_t *channel, + switch_caller_extension_t **extension, + osp_results_t *results) +{ + osp_destination_t *dest; + char allparam[OSP_SIZE_NORSTR]; + char eachparam[OSP_SIZE_NORSTR]; + char endpoint[OSP_SIZE_NORSTR]; + char buffer[OSP_SIZE_ROUSTR]; + int i; + + if ((*extension = switch_caller_extension_new(session, results->called, results->called)) == 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to create extension\n"); + return; + } + + switch_channel_set_variable(channel, SWITCH_HANGUP_AFTER_BRIDGE_VARIABLE, "true"); + + osp_build_allparam(results, allparam, sizeof(allparam)); + + for (i = 0; i < results->numdest; i++) { + dest = &results->dests[i]; + if (dest->supported) { + osp_build_eachparam(i + 1, dest, eachparam, sizeof(eachparam)); + osp_build_endpoint(dest, endpoint, sizeof(endpoint)); + switch_snprintf(buffer, sizeof(buffer), "%s%s%s", allparam, eachparam, endpoint); + switch_caller_extension_add_application(session, *extension, "bridge", buffer); + } + } +} + +/* + * Macro expands to: + * switch_caller_extension_t * osp_dialplan_function(switch_core_session_t *session, void *arg, switch_caller_profile_t *caller_profile) + */ +SWITCH_STANDARD_DIALPLAN(osp_dialplan_function) +{ + int argc = 0; + char *argv[2] = { 0 }; + char *profile = NULL; + switch_caller_extension_t *extension = NULL; + switch_channel_t *channel = switch_core_session_get_channel(session); + osp_results_t results; + switch_status_t retval; + + if (osp_globals.shutdown) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "OSP dialplan inavailable\n"); + return extension; + } + + if ((argc = switch_separate_string((char *)arg, ' ', argv, (sizeof(argv) / sizeof(argv[0]))))) { + profile = argv[0]; + } else { + profile = OSP_DEF_PROFILE; + } + + retval = osp_request_routing(channel, profile, &results); + if (retval == SWITCH_STATUS_SUCCESS) { + osp_add_application(session, channel, &extension, &results); + } + + return extension; +} + +/* + * Retrieve OSP cookie + * param channel Destination channel + * param cookie OSP cookie + * return SWITCH_STATUS_SUCCESS Successful, SWITCH_STATUS_FALSE Failed + */ +static switch_status_t osp_get_ospcookie( + switch_channel_t *channel, + osp_cookie_t *cookie) +{ + const char *strvar; + + if (!(cookie->profile = switch_channel_get_variable(channel, OSP_VAR_PROFILE))) { + return SWITCH_STATUS_FALSE; + } + + if ((strvar = switch_channel_get_variable(channel, OSP_VAR_TRANSID))) { + cookie->transid = atoll(strvar); + } else { + cookie->transid = 0; + } + + if ((strvar = switch_channel_get_variable(channel, OSP_VAR_START))) { + cookie->start = atoll(strvar); + } else { + cookie->start = 0; + } + + if ((strvar = switch_channel_get_variable(channel, OSP_VAR_DESTCOUNT))) { + cookie->destcount = atoi(strvar); + } else { + cookie->destcount = 0; + } + + cookie->dest = switch_channel_get_variable(channel, OSP_VAR_DESTIP); + + cookie->srcnid = switch_channel_get_variable(channel, OSP_VAR_SRCNID); + cookie->destnid = switch_channel_get_variable(channel, OSP_VAR_DESTNID); + + return SWITCH_STATUS_SUCCESS; +} + +/* + * Retrieve usage info + * param channel Destination channel + * param originator Originator channel + * param cookie OSP cookie + * param usage Usage info + * return + */ +static void osp_get_usage( + switch_channel_t *channel, + switch_caller_profile_t *originator, + osp_cookie_t *cookie, + osp_usage_t *usage) +{ + const char *strvar; + switch_caller_profile_t *terminator; + switch_channel_timetable_t *times; + + memset(usage, 0, sizeof(*usage)); + + usage->callid = switch_channel_get_variable(channel, OSP_FS_OUTCALLID); + if (switch_strlen_zero(usage->callid)) { + usage->callid = OSP_DEF_CALLID; + } + + /* Originator had been checked by osp_on_reporting */ + if (originator) { + usage->srcdev = originator->network_addr; + usage->calling = originator->caller_id_number; + osp_parse_userinfo(originator->destination_number, usage->called, sizeof(usage->called), NULL); + } + + terminator = switch_channel_get_caller_profile(channel); + if (!strcasecmp(terminator->source, OSP_MODULE_SIP)) { + usage->protocol = OSPC_DPROT_SIP; + strvar = switch_channel_get_variable(channel, OSP_FS_SIPRELEASE); + if (!strvar || !strcasecmp(strvar, "recv_bye")) { + usage->release = 1; + } + } else if (!strcasecmp(terminator->source, OSP_MODULE_H323)) { + usage->protocol = OSPC_DPROT_Q931; + } else if (!strcasecmp(terminator->source, OSP_MODULE_IAX)) { + usage->protocol = OSPC_DPROT_IAX; + } else if (!strcasecmp(terminator->source, OSP_MODULE_SKYPE)) { + usage->protocol = OSPC_DPROT_SKYPE; + } else { + usage->protocol = OSPC_DPROT_UNKNOWN; + } + + usage->cause = switch_channel_get_cause_q850(channel); + + times = switch_channel_get_timetable(channel); + usage->alert = times->progress; + usage->connect = times->answered; + usage->end = times->hungup; + if (times->answered) { + usage->duration = times->hungup - times->answered; + usage->pdd = times->answered - cookie->start; + } + + usage->fcodec = switch_channel_get_variable(channel, OSP_FS_DOWNCODEC); + usage->rcodec = switch_channel_get_variable(channel, OSP_FS_UPCODEC); + if ((strvar = switch_channel_get_variable(channel, OSP_FS_RTPDOWNOCTS))) { + usage->rtpdownoctets = atoi(strvar); + } else { + usage->rtpdownoctets = OSP_DEF_STATS; + } + if ((strvar = switch_channel_get_variable(channel, OSP_FS_RTPUPOCTS))) { + usage->rtpupoctets = atoi(strvar); + } else { + usage->rtpupoctets = OSP_DEF_STATS; + } + if ((strvar = switch_channel_get_variable(channel, OSP_FS_RTPDOWNPKTS))) { + usage->rtpdownpackets = atoi(strvar); + } else { + usage->rtpdownpackets = OSP_DEF_STATS; + } + if ((strvar = switch_channel_get_variable(channel, OSP_FS_RTPUPPKTS))) { + usage->rtpuppackets = atoi(strvar); + } else { + usage->rtpuppackets = OSP_DEF_STATS; + } +} + +/* + * Report OSP usage thread function + * param threadarg Thread argments + * return + */ +static OSPTTHREADRETURN osp_report_thread( + void *threadarg) +{ + int i, error; + osp_threadarg_t *info; + + info = (osp_threadarg_t *)threadarg; + + OSPPTransactionRecordFailure(info->handle, info->cause); + + for (i = 0; i < 3; i++) { + error = OSPPTransactionReportUsage( + info->handle, /* Transaction handle */ + info->duration, /* Call duration */ + info->start, /* Call start time */ + info->end, /* Call end time */ + info->alert, /* Call alert time */ + info->connect, /* Call connect time */ + info->pdd != 0, /* Post dial delay present */ + info->pdd, /* Post dial delay */ + info->release, /* Release source */ + NULL, /* Conference ID */ + -1, /* Packets not received by peer */ + -1, /* Fraction of packets not received by peer */ + -1, /* Packets not received that were expected */ + -1, /* Fraction of packets expected but not received */ + NULL, /* Log buffer size */ + NULL); /* Log buffer */ + if (error != OSPC_ERR_NO_ERROR) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, + "Failed to report usage for '%llu' attempt '%d'\n", + info->transid, + i + 1); + } else { + break; + } + } + + OSPPTransactionDelete(info->handle); + + switch_safe_free(info); + + OSPTTHREADRETURN_NULL(); +} + +/* + * Report usage + * param cookie OSP cookie + * param usage Usage + * return SWITCH_STATUS_SUCCESS Successful, SWITCH_STATUS_FALSE Failed + */ +static switch_status_t osp_report_usage( + osp_cookie_t *cookie, + osp_usage_t *usage) +{ + osp_provider_t *provider; + OSPTTRANHANDLE handle; + osp_threadarg_t *info; + OSPTTHREADID threadid; + OSPTTHRATTR threadattr; + int error; + switch_status_t status = SWITCH_STATUS_FALSE; + + if (osp_find_provider(cookie->profile, &provider) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to find provider '%s'\n", cookie->profile); + return status; + } + + if ((error = OSPPTransactionNew(provider->handle, &handle)) != OSPC_ERR_NO_ERROR) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create transaction handle, error '%d'\n", error); + return status; + } + + error = OSPPTransactionBuildUsageFromScratch( + handle, /* Transaction handle */ + cookie->transid, /* Transaction ID */ + OSPC_ROLE_SOURCE, /* CDR type, source */ + provider->device, /* Source */ + cookie->dest, /* Destination */ + usage->srcdev, /* Source device */ + OSP_DEF_STRING, /* Destination device */ + usage->calling, /* Calling */ + OSPC_NFORMAT_E164, /* Calling format */ + usage->called, /* Called */ + OSPC_NFORMAT_E164, /* Called format */ + strlen(usage->callid), /* Size of Call-ID */ + usage->callid, /* Call-ID */ + 0, /* Failure reason */ + NULL, /* Log buffer size */ + NULL); /* Log buffer */ + if (error != OSPC_ERR_NO_ERROR) { + OSPPTransactionDelete(handle); + return status; + } + + status = SWITCH_STATUS_SUCCESS; + + OSPPTransactionSetDestinationCount(handle, cookie->destcount); + + if (!switch_strlen_zero(cookie->srcnid)) { + OSPPTransactionSetSrcNetworkId(handle, cookie->srcnid); + } + + if (!switch_strlen_zero(cookie->destnid)) { + OSPPTransactionSetDestNetworkId(handle, cookie->destnid); + } + + if (!switch_strlen_zero(usage->fcodec)) { + OSPPTransactionSetForwardCodec(handle, usage->fcodec); + } + if (!switch_strlen_zero(usage->rcodec)) { + OSPPTransactionSetReverseCodec(handle, usage->rcodec); + } + + if (usage->rtpdownoctets != OSP_DEF_STATS) { + OSPPTransactionSetOctets(handle, OSPC_SMETRIC_RTP, OSPC_SFLOW_DOWNSTREAM, usage->rtpdownoctets); + } + if (usage->rtpupoctets != OSP_DEF_STATS) { + OSPPTransactionSetOctets(handle, OSPC_SMETRIC_RTP, OSPC_SFLOW_UPSTREAM, usage->rtpupoctets); + } + if (usage->rtpdownpackets != OSP_DEF_STATS) { + OSPPTransactionSetPackets(handle, OSPC_SMETRIC_RTP, OSPC_SFLOW_DOWNSTREAM, usage->rtpdownpackets); + } + if (usage->rtpuppackets != OSP_DEF_STATS) { + OSPPTransactionSetPackets(handle, OSPC_SMETRIC_RTP, OSPC_SFLOW_UPSTREAM, usage->rtpuppackets); + } + + info = (osp_threadarg_t *)malloc(sizeof(osp_threadarg_t)); + info->handle = handle; + info->transid = cookie->transid; + info->cause = usage->cause; + info->start = cookie->start / 1000000; + info->alert = usage->alert / 1000000; + info->connect = usage->connect / 1000000; + info->end = usage->end / 1000000; + info->duration = usage->duration / 1000000; + info->pdd = usage->pdd / 1000000; + info->release = usage->release; + + OSPM_THRATTR_INIT(threadattr, error); + OSPM_SETDETACHED_STATE(threadattr, error); + OSPM_CREATE_THREAD(threadid, &threadattr, osp_report_thread, info, error); + OSPM_THRATTR_DESTROY(threadattr); + + /* handle and info will be released by osp_report_thread */ + + return status; +} + +/* + * Log UsageInd parameters + * param cookie OSP cookie + * param usage Usage info + * return + */ +static void osp_log_usageind( + osp_cookie_t *cookie, + osp_usage_t *usage) +{ + if (osp_globals.debug) { + switch_log_printf(SWITCH_CHANNEL_LOG, osp_globals.loglevel, + "UsageInd: " + "transid = '%llu' " + "destcount = '%d' " + "callid = '%s' " + "calling = '%s' " + "called = '%s' " + "srcdev = '%s' " + "dest = '%s' " + "nid = '%s/%s' " + "protocol = '%s' " + "cause = '%d' " + "release = '%s' " + "times = '%llu/%llu/%llu/%llu' " + "duration = '%llu' " + "pdd = '%llu' " + "outsessionid = '%s' " + "codec = '%s/%s' " + "rtpctets = '%d/%d' " + "rtppackets = '%d/%d'\n", + cookie->transid, + cookie->destcount, + usage->callid, + usage->calling, + usage->called, + usage->srcdev, + cookie->dest, + osp_filter_null(cookie->srcnid), osp_filter_null(cookie->destnid), + osp_get_protocol(usage->protocol), + usage->cause, + usage->release ? "term" : "orig", + cookie->start / 1000000, usage->alert / 1000000, usage->connect / 1000000, usage->end / 1000000, + usage->duration / 1000000, + usage->pdd / 1000000, + usage->callid, + osp_filter_null(usage->fcodec), osp_filter_null(usage->rcodec), + usage->rtpdownoctets, usage->rtpupoctets, + usage->rtpdownpackets, usage->rtpuppackets); + } +} + +/* + * OSP module CS_REPORTING state handler + * param session Session + * return SWITCH_STATUS_SUCCESS Successful, SWITCH_STATUS_FALSE Failed + */ +static switch_status_t osp_on_reporting( + switch_core_session_t *session) +{ + switch_channel_t *channel; + osp_cookie_t cookie; + osp_usage_t usage; + switch_caller_profile_t *originator; + switch_status_t status = SWITCH_STATUS_SUCCESS; + + if (osp_globals.shutdown) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "OSP application inavailable\n"); + return status; + } + + /* Only report for B-leg */ + if (!(channel = switch_core_session_get_channel(session)) || !(originator = switch_channel_get_originator_caller_profile(channel))) { + return status; + } + + if (osp_get_ospcookie(channel, &cookie) != SWITCH_STATUS_SUCCESS) { + return status; + } + + osp_get_usage(channel, originator, &cookie, &usage); + + osp_log_usageind(&cookie, &usage); + + osp_report_usage(&cookie, &usage); + + return status; +} + +/* + * OSP module state handlers + */ +static switch_state_handler_table_t state_handlers = { + NULL, /*.on_init */ + NULL, /*.on_routing */ + NULL, /*.on_execute */ + NULL, /*.on_hangup */ + NULL, /*.on_exchange_media */ + NULL, /*.on_soft_execute */ + NULL, /*.on_consume_media */ + NULL, /*.on_hibernate */ + NULL, /*.on_reset */ + NULL, /*.on_park */ + osp_on_reporting /*.on_reporting */ +}; + +/* + * Macro expands to: + * switch_status_t mod_osp_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool) + */ +SWITCH_MODULE_LOAD_FUNCTION(mod_osp_load) +{ + switch_api_interface_t *api_interface; + switch_application_interface_t *app_interface; + switch_dialplan_interface_t *dp_interface; + switch_status_t status = SWITCH_STATUS_SUCCESS; + + /* Load OSP configuration */ + if ((status = osp_load_settings(pool)) != SWITCH_STATUS_SUCCESS) { + return status; + } + + /* Init OSP Toolkit */ + osp_init_osptk(); + + /* Connect OSP internal structure to the blank pointer passed to OSP module */ + *module_interface = switch_loadable_module_create_module_interface(pool, modname); + + /* Add CLI OSP command */ + SWITCH_ADD_API(api_interface, "osp", "OSP", osp_api_function, "status"); + switch_console_set_complete("add osp status"); + + /* Add OSP application */ + SWITCH_ADD_APP(app_interface, "osp", "Perform an OSP lookup", "Perform an OSP lookup", osp_app_function, "", SAF_SUPPORT_NOMEDIA | SAF_ROUTING_EXEC); + + /* Add OSP dialplan */ + SWITCH_ADD_DIALPLAN(dp_interface, "osp", osp_dialplan_function); + + /* Add OSP state handlers */ + switch_core_add_state_handler(&state_handlers); + + /* Indicate that the module should continue to be loaded */ + return status; +} + +/* + * Cleanup OSP client end + * return + */ +static void osp_cleanup_osptk(void) +{ + osp_provider_t *provider; + + for (provider = osp_providers; provider; provider = provider->next) { + OSPPProviderDelete(provider->handle, 0); + provider->handle = OSP_INVALID_HANDLE; + } + + OSPPCleanup(); +} + +/* + * Called when the system shuts down + * Macro expands to: + * switch_status_t mod_osp_shutdown(void) + */ +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_osp_shutdown) +{ + /* Shutdown OSP module */ + osp_globals.shutdown = SWITCH_TRUE; + + /* Cleanup OSP Toolkit */ + osp_cleanup_osptk(); + + /* Remoeve OSP state handlers */ + switch_core_remove_state_handler(&state_handlers); + + 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 + */ +