#include "sip/include/config_parser.h"
#include "sip/include/reqresp_parser.h"
#include "sip/include/sip_utils.h"
+#include "asterisk/ccss.h"
+#include "asterisk/xml.h"
#include "sip/include/dialog.h"
#include "sip/include/dialplan_functions.h"
{ SIP_UPDATE, NO_RTP, "UPDATE", CAN_NOT_CREATE_DIALOG },
{ SIP_INFO, NO_RTP, "INFO", CAN_NOT_CREATE_DIALOG },
{ SIP_CANCEL, NO_RTP, "CANCEL", CAN_NOT_CREATE_DIALOG },
- { SIP_PUBLISH, NO_RTP, "PUBLISH", CAN_CREATE_DIALOG_UNSUPPORTED_METHOD },
+ { SIP_PUBLISH, NO_RTP, "PUBLISH", CAN_CREATE_DIALOG },
{ SIP_PING, NO_RTP, "PING", CAN_CREATE_DIALOG_UNSUPPORTED_METHOD }
};
static int global_dynamic_exclude_static = 0; /*!< Exclude static peers from contact registrations */
/*@}*/
+/*!
+ * We use libxml2 in order to parse XML that may appear in the body of a SIP message. Currently,
+ * the only usage is for parsing PIDF bodies of incoming PUBLISH requests in the call-completion
+ * event package. This variable is set at module load time and may be checked at runtime to determine
+ * if XML parsing support was found.
+ */
+static int can_parse_xml;
+
/*! \name Object counters @{
* \bug These counters are not handled in a thread-safe way ast_atomic_fetchadd_int()
* should be used to modify these values. */
static const int HASH_DIALOG_SIZE = 563;
#endif
+static const struct {
+ enum ast_cc_service_type service;
+ const char *service_string;
+} sip_cc_service_map [] = {
+ [AST_CC_NONE] = { AST_CC_NONE, "" },
+ [AST_CC_CCBS] = { AST_CC_CCBS, "BS" },
+ [AST_CC_CCNR] = { AST_CC_CCNR, "NR" },
+ [AST_CC_CCNL] = { AST_CC_CCNL, "NL" },
+};
+
+static enum ast_cc_service_type service_string_to_service_type(const char * const service_string)
+{
+ enum ast_cc_service_type service;
+ for (service = AST_CC_CCBS; service <= AST_CC_CCNL; ++service) {
+ if (!strcasecmp(service_string, sip_cc_service_map[service].service_string)) {
+ return service;
+ }
+ }
+ return AST_CC_NONE;
+}
+
+static const struct {
+ enum sip_cc_notify_state state;
+ const char *state_string;
+} sip_cc_notify_state_map [] = {
+ [CC_QUEUED] = {CC_QUEUED, "cc-state: queued"},
+ [CC_READY] = {CC_READY, "cc-state: ready"},
+};
+
+AST_LIST_HEAD_STATIC(epa_static_data_list, epa_backend);
+
+static int sip_epa_register(const struct epa_static_data *static_data)
+{
+ struct epa_backend *backend = ast_calloc(1, sizeof(*backend));
+
+ if (!backend) {
+ return -1;
+ }
+
+ backend->static_data = static_data;
+
+ AST_LIST_LOCK(&epa_static_data_list);
+ AST_LIST_INSERT_TAIL(&epa_static_data_list, backend, next);
+ AST_LIST_UNLOCK(&epa_static_data_list);
+ return 0;
+}
+
+static void cc_handle_publish_error(struct sip_pvt *pvt, const int resp, struct sip_request *req, struct sip_epa_entry *epa_entry);
+
+static void cc_epa_destructor(void *data)
+{
+ struct sip_epa_entry *epa_entry = data;
+ struct cc_epa_entry *cc_entry = epa_entry->instance_data;
+ ast_free(cc_entry);
+}
+
+static const struct epa_static_data cc_epa_static_data = {
+ .event = CALL_COMPLETION,
+ .name = "call-completion",
+ .handle_error = cc_handle_publish_error,
+ .destructor = cc_epa_destructor,
+};
+
+static const struct epa_static_data *find_static_data(const char * const event_package)
+{
+ const struct epa_backend *backend = NULL;
+
+ AST_LIST_LOCK(&epa_static_data_list);
+ AST_LIST_TRAVERSE(&epa_static_data_list, backend, next) {
+ if (!strcmp(backend->static_data->name, event_package)) {
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(&epa_static_data_list);
+ return backend ? backend->static_data : NULL;
+}
+
+static struct sip_epa_entry *create_epa_entry (const char * const event_package, const char * const destination)
+{
+ struct sip_epa_entry *epa_entry;
+ const struct epa_static_data *static_data;
+
+ if (!(static_data = find_static_data(event_package))) {
+ return NULL;
+ }
+
+ if (!(epa_entry = ao2_t_alloc(sizeof(*epa_entry), static_data->destructor, "Allocate new EPA entry"))) {
+ return NULL;
+ }
+
+ epa_entry->static_data = static_data;
+ ast_copy_string(epa_entry->destination, destination, sizeof(epa_entry->destination));
+ return epa_entry;
+}
+
+/*!
+ * Used to create new entity IDs by ESCs.
+ */
+static int esc_etag_counter;
+static const int DEFAULT_PUBLISH_EXPIRES = 3600;
+
+#ifdef HAVE_LIBXML2
+static int cc_esc_publish_handler(struct sip_pvt *pvt, struct sip_request *req, struct event_state_compositor *esc, struct sip_esc_entry *esc_entry);
+
+static const struct sip_esc_publish_callbacks cc_esc_publish_callbacks = {
+ .initial_handler = cc_esc_publish_handler,
+ .modify_handler = cc_esc_publish_handler,
+};
+#endif
+
+/*!
+ * \brief The Event State Compositors
+ *
+ * An Event State Compositor is an entity which
+ * accepts PUBLISH requests and acts appropriately
+ * based on these requests.
+ *
+ * The actual event_state_compositor structure is simply
+ * an ao2_container of sip_esc_entrys. When an incoming
+ * PUBLISH is received, we can match the appropriate sip_esc_entry
+ * using the entity ID of the incoming PUBLISH.
+ */
+static struct event_state_compositor {
+ enum subscriptiontype event;
+ const char * name;
+ const struct sip_esc_publish_callbacks *callbacks;
+ struct ao2_container *compositor;
+} event_state_compositors [] = {
+#ifdef HAVE_LIBXML2
+ {CALL_COMPLETION, "call-completion", &cc_esc_publish_callbacks},
+#endif
+};
+
+static const int ESC_MAX_BUCKETS = 37;
+
+static void esc_entry_destructor(void *obj)
+{
+ struct sip_esc_entry *esc_entry = obj;
+ if (esc_entry->sched_id > -1) {
+ AST_SCHED_DEL(sched, esc_entry->sched_id);
+ }
+}
+
+static int esc_hash_fn(const void *obj, const int flags)
+{
+ const struct sip_esc_entry *entry = obj;
+ return ast_str_hash(entry->entity_tag);
+}
+
+static int esc_cmp_fn(void *obj, void *arg, int flags)
+{
+ struct sip_esc_entry *entry1 = obj;
+ struct sip_esc_entry *entry2 = arg;
+
+ return (!strcmp(entry1->entity_tag, entry2->entity_tag)) ? (CMP_MATCH | CMP_STOP) : 0;
+}
+
+static struct event_state_compositor *get_esc(const char * const event_package) {
+ int i;
+ for (i = 0; i < ARRAY_LEN(event_state_compositors); i++) {
+ if (!strcasecmp(event_package, event_state_compositors[i].name)) {
+ return &event_state_compositors[i];
+ }
+ }
+ return NULL;
+}
+
+static struct sip_esc_entry *get_esc_entry(const char * entity_tag, struct event_state_compositor *esc) {
+ struct sip_esc_entry *entry;
+ struct sip_esc_entry finder;
+
+ ast_copy_string(finder.entity_tag, entity_tag, sizeof(finder.entity_tag));
+
+ entry = ao2_find(esc->compositor, &finder, OBJ_POINTER);
+
+ return entry;
+}
+
+static int publish_expire(const void *data)
+{
+ struct sip_esc_entry *esc_entry = (struct sip_esc_entry *) data;
+ struct event_state_compositor *esc = get_esc(esc_entry->event);
+
+ ast_assert(esc != NULL);
+
+ ao2_unlink(esc->compositor, esc_entry);
+ ao2_ref(esc_entry, -1);
+ return 0;
+}
+
+static void create_new_sip_etag(struct sip_esc_entry *esc_entry, int is_linked)
+{
+ int new_etag = ast_atomic_fetchadd_int(&esc_etag_counter, +1);
+ struct event_state_compositor *esc = get_esc(esc_entry->event);
+
+ ast_assert(esc != NULL);
+ if (is_linked) {
+ ao2_unlink(esc->compositor, esc_entry);
+ }
+ snprintf(esc_entry->entity_tag, sizeof(esc_entry->entity_tag), "%d", new_etag);
+ ao2_link(esc->compositor, esc_entry);
+}
+
+static struct sip_esc_entry *create_esc_entry(struct event_state_compositor *esc, struct sip_request *req, const int expires)
+{
+ struct sip_esc_entry *esc_entry;
+ int expires_ms;
+
+ if (!(esc_entry = ao2_alloc(sizeof(*esc_entry), esc_entry_destructor))) {
+ return NULL;
+ }
+
+ esc_entry->event = esc->name;
+
+ expires_ms = expires * 1000;
+ /* Bump refcount for scheduler */
+ ao2_ref(esc_entry, +1);
+ esc_entry->sched_id = ast_sched_add(sched, expires_ms, publish_expire, esc_entry);
+
+ /* Note: This links the esc_entry into the ESC properly */
+ create_new_sip_etag(esc_entry, 0);
+
+ return esc_entry;
+}
+
+static int initialize_escs(void)
+{
+ int i, res = 0;
+ for (i = 0; i < ARRAY_LEN(event_state_compositors); i++) {
+ if (!((event_state_compositors[i].compositor) =
+ ao2_container_alloc(ESC_MAX_BUCKETS, esc_hash_fn, esc_cmp_fn))) {
+ res = -1;
+ }
+ }
+ return res;
+}
+
+static void destroy_escs(void)
+{
+ int i;
+ for (i = 0; i < ARRAY_LEN(event_state_compositors); i++) {
+ ao2_ref(event_state_compositors[i].compositor, -1);
+ }
+}
+
/*! \brief
* Here we implement the container for dialogs (sip_pvt), defining
* generic wrapper functions to ease the transition from the current
static int sipsock_read(int *id, int fd, short events, void *ignore);
static int __sip_xmit(struct sip_pvt *p, struct ast_str *data, int len);
static int __sip_reliable_xmit(struct sip_pvt *p, int seqno, int resp, struct ast_str *data, int len, int fatal, int sipmethod);
+static void add_cc_call_info_to_response(struct sip_pvt *p, struct sip_request *resp);
static int __transmit_response(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable);
static int retrans_pkt(const void *data);
static int transmit_response_using_temp(ast_string_field callid, struct sockaddr_in *sin, int useglobal_nat, const int intended_method, const struct sip_request *req, const char *msg);
static void transmit_fake_auth_response(struct sip_pvt *p, int sipmethod, struct sip_request *req, enum xmittype reliable);
static int transmit_request(struct sip_pvt *p, int sipmethod, int inc, enum xmittype reliable, int newbranch);
static int transmit_request_with_auth(struct sip_pvt *p, int sipmethod, int seqno, enum xmittype reliable, int newbranch);
-static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init);
+static int transmit_publish(struct sip_epa_entry *epa_entry, enum sip_publish_type publish_type, const char * const explicit_uri);
+static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init, const char * const explicit_uri);
static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version, int oldsdp);
static int transmit_info_with_digit(struct sip_pvt *p, const char digit, unsigned int duration);
static int transmit_info_with_vidupdate(struct sip_pvt *p);
static int transmit_refer(struct sip_pvt *p, const char *dest);
static int transmit_notify_with_mwi(struct sip_pvt *p, int newmsgs, int oldmsgs, const char *vmexten);
static int transmit_notify_with_sipfrag(struct sip_pvt *p, int cseq, char *message, int terminate);
+static int transmit_cc_notify(struct ast_cc_agent *agent, struct sip_pvt *subscription, enum sip_cc_notify_state state);
static int transmit_register(struct sip_registry *r, int sipmethod, const char *auth, const char *authheader);
static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno);
static int send_request(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno);
static void check_via(struct sip_pvt *p, struct sip_request *req);
static int get_rpid(struct sip_pvt *p, struct sip_request *oreq);
static int get_rdnis(struct sip_pvt *p, struct sip_request *oreq, char **name, char **number, int *reason);
-static int get_destination(struct sip_pvt *p, struct sip_request *oreq);
+static int get_destination(struct sip_pvt *p, struct sip_request *oreq, int *cc_recall_core_id);
static int get_msg_text(char *buf, int len, struct sip_request *req, int addnewline);
static int transmit_state_notify(struct sip_pvt *p, int state, int full, int timeout);
static void update_connectedline(struct sip_pvt *p, const void *data, size_t datalen);
static void initialize_initreq(struct sip_pvt *p, struct sip_request *req);
static int init_req(struct sip_request *req, int sipmethod, const char *recip);
static int reqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod, int seqno, int newbranch);
-static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod);
+static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod, const char * const explicit_uri);
static int init_resp(struct sip_request *resp, const char *msg);
static inline int resp_needs_contact(const char *msg, enum sipmethod method);
static int respprep(struct sip_request *resp, struct sip_pvt *p, const char *msg, const struct sip_request *req);
static int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual *current, struct sip_request *req, int seqno, int *nounlock);
/*------Response handling functions */
+static void handle_response_publish(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno);
static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno);
static void handle_response_notify(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno);
static void handle_response_refer(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno);
*/
struct ast_channel_tech sip_tech_info;
-/*! \brief Working TLS connection configuration */
-static struct ast_tls_config sip_tls_cfg;
-
-/*! \brief Default TLS connection configuration */
-static struct ast_tls_config default_tls_cfg;
+static int sip_cc_agent_init(struct ast_cc_agent *agent, struct ast_channel *chan);
+static int sip_cc_agent_start_offer_timer(struct ast_cc_agent *agent);
+static int sip_cc_agent_stop_offer_timer(struct ast_cc_agent *agent);
+static void sip_cc_agent_ack(struct ast_cc_agent *agent);
+static int sip_cc_agent_status_request(struct ast_cc_agent *agent);
+static int sip_cc_agent_start_monitoring(struct ast_cc_agent *agent);
+static int sip_cc_agent_recall(struct ast_cc_agent *agent);
+static void sip_cc_agent_destructor(struct ast_cc_agent *agent);
-/*! \brief The TCP server definition */
-static struct ast_tcptls_session_args sip_tcp_desc = {
- .accept_fd = -1,
- .master = AST_PTHREADT_NULL,
- .tls_cfg = NULL,
- .poll_timeout = -1,
- .name = "SIP TCP server",
- .accept_fn = ast_tcptls_server_root,
- .worker_fn = sip_tcp_worker_fn,
+static struct ast_cc_agent_callbacks sip_cc_agent_callbacks = {
+ .type = "SIP",
+ .init = sip_cc_agent_init,
+ .start_offer_timer = sip_cc_agent_start_offer_timer,
+ .stop_offer_timer = sip_cc_agent_stop_offer_timer,
+ .ack = sip_cc_agent_ack,
+ .status_request = sip_cc_agent_status_request,
+ .start_monitoring = sip_cc_agent_start_monitoring,
+ .callee_available = sip_cc_agent_recall,
+ .destructor = sip_cc_agent_destructor,
};
-/*! \brief The TCP/TLS server definition */
-static struct ast_tcptls_session_args sip_tls_desc = {
- .accept_fd = -1,
- .master = AST_PTHREADT_NULL,
- .tls_cfg = &sip_tls_cfg,
- .poll_timeout = -1,
- .name = "SIP TLS server",
- .accept_fn = ast_tcptls_server_root,
- .worker_fn = sip_tcp_worker_fn,
-};
+static int find_by_notify_uri_helper(void *obj, void *arg, int flags)
+{
+ struct ast_cc_agent *agent = obj;
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ const char *uri = arg;
-/*! \brief Append to SIP dialog history
- \return Always returns 0 */
-#define append_history(p, event, fmt , args... ) append_history_full(p, "%-15s " fmt, event, ## args)
+ return !strcmp(agent_pvt->notify_uri, uri) ? CMP_MATCH | CMP_STOP : 0;
+}
-struct sip_pvt *dialog_ref_debug(struct sip_pvt *p, char *tag, char *file, int line, const char *func)
+static struct ast_cc_agent *find_sip_cc_agent_by_notify_uri(const char * const uri)
{
- if (p)
-#ifdef REF_DEBUG
- __ao2_ref_debug(p, 1, tag, file, line, func);
-#else
- ao2_ref(p, 1);
-#endif
- else
- ast_log(LOG_ERROR, "Attempt to Ref a null pointer\n");
- return p;
+ struct ast_cc_agent *agent = ast_cc_agent_callback(0, find_by_notify_uri_helper, (char *)uri, "SIP");
+ return agent;
}
-struct sip_pvt *dialog_unref_debug(struct sip_pvt *p, char *tag, char *file, int line, const char *func)
+static int find_by_subscribe_uri_helper(void *obj, void *arg, int flags)
{
- if (p)
-#ifdef REF_DEBUG
- __ao2_ref_debug(p, -1, tag, file, line, func);
-#else
- ao2_ref(p, -1);
-#endif
- return NULL;
+ struct ast_cc_agent *agent = obj;
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ const char *uri = arg;
+
+ return !strcmp(agent_pvt->subscribe_uri, uri) ? CMP_MATCH | CMP_STOP : 0;
}
-/*! \brief map from an integer value to a string.
- * If no match is found, return errorstring
- */
-static const char *map_x_s(const struct _map_x_s *table, int x, const char *errorstring)
+static struct ast_cc_agent *find_sip_cc_agent_by_subscribe_uri(const char * const uri)
{
- const struct _map_x_s *cur;
-
- for (cur = table; cur->s; cur++)
- if (cur->x == x)
- return cur->s;
- return errorstring;
+ struct ast_cc_agent *agent = ast_cc_agent_callback(0, find_by_subscribe_uri_helper, (char *)uri, "SIP");
+ return agent;
}
-/*! \brief map from a string to an integer value, case insensitive.
- * If no match is found, return errorvalue.
- */
-static int map_s_x(const struct _map_x_s *table, const char *s, int errorvalue)
+static int find_by_callid_helper(void *obj, void *arg, int flags)
{
- const struct _map_x_s *cur;
+ struct ast_cc_agent *agent = obj;
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ struct sip_pvt *call_pvt = arg;
- for (cur = table; cur->s; cur++)
- if (!strcasecmp(cur->s, s))
- return cur->x;
- return errorvalue;
+ return !strcmp(agent_pvt->original_callid, call_pvt->callid) ? CMP_MATCH | CMP_STOP : 0;
}
-static enum AST_REDIRECTING_REASON sip_reason_str_to_code(const char *text)
+static struct ast_cc_agent *find_sip_cc_agent_by_original_callid(struct sip_pvt *pvt)
{
- enum AST_REDIRECTING_REASON ast = AST_REDIRECTING_REASON_UNKNOWN;
- int i;
+ struct ast_cc_agent *agent = ast_cc_agent_callback(0, find_by_callid_helper, pvt, "SIP");
+ return agent;
+}
- for (i = 0; i < ARRAY_LEN(sip_reason_table); ++i) {
- if (!strcasecmp(text, sip_reason_table[i].text)) {
- ast = sip_reason_table[i].code;
- break;
- }
+static int sip_cc_agent_init(struct ast_cc_agent *agent, struct ast_channel *chan)
+{
+ struct sip_cc_agent_pvt *agent_pvt = ast_calloc(1, sizeof(*agent_pvt));
+ struct sip_pvt *call_pvt = chan->tech_pvt;
+
+ if (!agent_pvt) {
+ return -1;
}
- return ast;
+ ast_assert(!strcmp(chan->tech->type, "SIP"));
+
+ ast_copy_string(agent_pvt->original_callid, call_pvt->callid, sizeof(agent_pvt->original_callid));
+ ast_copy_string(agent_pvt->original_exten, call_pvt->exten, sizeof(agent_pvt->original_exten));
+ agent_pvt->offer_timer_id = -1;
+ agent->private_data = agent_pvt;
+ sip_pvt_lock(call_pvt);
+ ast_set_flag(&call_pvt->flags[0], SIP_OFFER_CC);
+ sip_pvt_unlock(call_pvt);
+ return 0;
}
-static const char *sip_reason_code_to_str(enum AST_REDIRECTING_REASON code)
+static int sip_offer_timer_expire(const void *data)
{
- if (code >= 0 && code < ARRAY_LEN(sip_reason_table)) {
- return sip_reason_table[code].text;
- }
+ struct ast_cc_agent *agent = (struct ast_cc_agent *) data;
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
- return "unknown";
+ agent_pvt->offer_timer_id = -1;
+
+ return ast_cc_failed(agent->core_id, "SIP agent %s's offer timer expired", agent->device_name);
}
-/*!
- * \brief generic function for determining if a correct transport is being
- * used to contact a peer
- *
- * this is done as a macro so that the "tmpl" var can be passed either a
- * sip_request or a sip_peer
- */
-#define check_request_transport(peer, tmpl) ({ \
- int ret = 0; \
- if (peer->socket.type == tmpl->socket.type) \
- ; \
- else if (!(peer->transports & tmpl->socket.type)) {\
- ast_log(LOG_ERROR, \
- "'%s' is not a valid transport for '%s'. we only use '%s'! ending call.\n", \
- get_transport(tmpl->socket.type), peer->name, get_transport_list(peer->transports) \
- ); \
- ret = 1; \
- } else if (peer->socket.type & SIP_TRANSPORT_TLS) { \
- ast_log(LOG_WARNING, \
- "peer '%s' HAS NOT USED (OR SWITCHED TO) TLS in favor of '%s' (but this was allowed in sip.conf)!\n", \
- peer->name, get_transport(tmpl->socket.type) \
- ); \
- } else { \
- ast_debug(1, \
- "peer '%s' has contacted us over %s even though we prefer %s.\n", \
- peer->name, get_transport(tmpl->socket.type), get_transport(peer->socket.type) \
- ); \
- }\
- (ret); \
-})
+static int sip_cc_agent_start_offer_timer(struct ast_cc_agent *agent)
+{
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ int when;
-/*! \brief
- * duplicate a list of channel variables, \return the copy.
- */
-static struct ast_variable *copy_vars(struct ast_variable *src)
+ when = ast_get_cc_offer_timer(agent->cc_params) * 1000;
+ agent_pvt->offer_timer_id = ast_sched_add(sched, when, sip_offer_timer_expire, agent);
+ return 0;
+}
+
+static int sip_cc_agent_stop_offer_timer(struct ast_cc_agent *agent)
{
- struct ast_variable *res = NULL, *tmp, *v = NULL;
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
- for (v = src ; v ; v = v->next) {
- if ((tmp = ast_variable_new(v->name, v->value, v->file))) {
- tmp->next = res;
- res = tmp;
- }
- }
- return res;
+ AST_SCHED_DEL(sched, agent_pvt->offer_timer_id);
+ return 0;
}
-static void tcptls_packet_destructor(void *obj)
+static void sip_cc_agent_ack(struct ast_cc_agent *agent)
{
- struct tcptls_packet *packet = obj;
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
- ast_free(packet->data);
+ sip_pvt_lock(agent_pvt->subscribe_pvt);
+ ast_set_flag(&agent_pvt->subscribe_pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
+ transmit_response(agent_pvt->subscribe_pvt, "200 OK", &agent_pvt->subscribe_pvt->initreq);
+ transmit_cc_notify(agent, agent_pvt->subscribe_pvt, CC_QUEUED);
+ sip_pvt_unlock(agent_pvt->subscribe_pvt);
+ agent_pvt->is_available = TRUE;
}
-static void sip_tcptls_client_args_destructor(void *obj)
+static int sip_cc_agent_status_request(struct ast_cc_agent *agent)
{
- struct ast_tcptls_session_args *args = obj;
- if (args->tls_cfg) {
- ast_free(args->tls_cfg->certfile);
- ast_free(args->tls_cfg->pvtfile);
- ast_free(args->tls_cfg->cipher);
- ast_free(args->tls_cfg->cafile);
- ast_free(args->tls_cfg->capath);
- }
- ast_free(args->tls_cfg);
- ast_free((char *) args->name);
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ enum ast_device_state state = agent_pvt->is_available ? AST_DEVICE_NOT_INUSE : AST_DEVICE_INUSE;
+ return ast_cc_agent_status_response(agent->core_id, state);
}
-static void sip_threadinfo_destructor(void *obj)
+static int sip_cc_agent_start_monitoring(struct ast_cc_agent *agent)
{
- struct sip_threadinfo *th = obj;
- struct tcptls_packet *packet;
- if (th->alert_pipe[1] > -1) {
- close(th->alert_pipe[0]);
+ /* To start monitoring just means to wait for an incoming PUBLISH
+ * to tell us that the caller has become available again. No special
+ * action is needed
+ */
+ return 0;
+}
+
+static int sip_cc_agent_recall(struct ast_cc_agent *agent)
+{
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ /* If we have received a PUBLISH beforehand stating that the caller in question
+ * is not available, we can save ourself a bit of effort here and just report
+ * the caller as busy
+ */
+ if (!agent_pvt->is_available) {
+ return ast_cc_agent_caller_busy(agent->core_id, "Caller %s is busy, reporting to the core",
+ agent->device_name);
}
- if (th->alert_pipe[1] > -1) {
- close(th->alert_pipe[1]);
+ /* Otherwise, we transmit a NOTIFY to the caller and await either
+ * a PUBLISH or an INVITE
+ */
+ sip_pvt_lock(agent_pvt->subscribe_pvt);
+ transmit_cc_notify(agent, agent_pvt->subscribe_pvt, CC_READY);
+ sip_pvt_unlock(agent_pvt->subscribe_pvt);
+ return 0;
+}
+
+static void sip_cc_agent_destructor(struct ast_cc_agent *agent)
+{
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+
+ if (!agent_pvt) {
+ /* The agent constructor probably failed. */
+ return;
}
- th->alert_pipe[0] = th->alert_pipe[1] = -1;
- while ((packet = AST_LIST_REMOVE_HEAD(&th->packet_q, entry))) {
- ao2_t_ref(packet, -1, "thread destruction, removing packet from frame queue");
+ sip_cc_agent_stop_offer_timer(agent);
+ if (agent_pvt->subscribe_pvt) {
+ sip_pvt_lock(agent_pvt->subscribe_pvt);
+ if (!ast_test_flag(&agent_pvt->subscribe_pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED)) {
+ /* If we haven't sent a 200 OK for the SUBSCRIBE dialog yet, then we need to send a response letting
+ * the subscriber know something went wrong
+ */
+ transmit_response(agent_pvt->subscribe_pvt, "500 Internal Server Error", &agent_pvt->subscribe_pvt->initreq);
+ }
+ sip_pvt_unlock(agent_pvt->subscribe_pvt);
+ agent_pvt->subscribe_pvt = dialog_unref(agent_pvt->subscribe_pvt, "SIP CC agent destructor: Remove ref to subscription");
}
+ ast_free(agent_pvt);
+}
- if (th->tcptls_session) {
- ao2_t_ref(th->tcptls_session, -1, "remove tcptls_session for sip_threadinfo object");
+struct ao2_container *sip_monitor_instances;
+
+static int sip_monitor_instance_hash_fn(const void *obj, const int flags)
+{
+ const struct sip_monitor_instance *monitor_instance = obj;
+ return monitor_instance->core_id;
+}
+
+static int sip_monitor_instance_cmp_fn(void *obj, void *arg, int flags)
+{
+ struct sip_monitor_instance *monitor_instance1 = obj;
+ struct sip_monitor_instance *monitor_instance2 = arg;
+
+ return monitor_instance1->core_id == monitor_instance2->core_id ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static void sip_monitor_instance_destructor(void *data)
+{
+ struct sip_monitor_instance *monitor_instance = data;
+ if (monitor_instance->subscription_pvt) {
+ sip_pvt_lock(monitor_instance->subscription_pvt);
+ monitor_instance->subscription_pvt->expiry = 0;
+ transmit_invite(monitor_instance->subscription_pvt, SIP_SUBSCRIBE, FALSE, 0, monitor_instance->subscribe_uri);
+ sip_pvt_unlock(monitor_instance->subscription_pvt);
+ dialog_unref(monitor_instance->subscription_pvt, "Unref monitor instance ref of subscription pvt");
+ }
+ if (monitor_instance->suspension_entry) {
+ monitor_instance->suspension_entry->body[0] = '\0';
+ transmit_publish(monitor_instance->suspension_entry, SIP_PUBLISH_REMOVE ,monitor_instance->notify_uri);
+ ao2_t_ref(monitor_instance->suspension_entry, -1, "Decrementing suspension entry refcount in sip_monitor_instance_destructor");
}
+ ast_string_field_free_memory(monitor_instance);
}
-/*! \brief creates a sip_threadinfo object and links it into the threadt table. */
-static struct sip_threadinfo *sip_threadinfo_create(struct ast_tcptls_session_instance *tcptls_session, int transport)
+static struct sip_monitor_instance *sip_monitor_instance_init(int core_id, const char * const subscribe_uri, const char * const peername, const char * const device_name)
{
- struct sip_threadinfo *th;
+ struct sip_monitor_instance *monitor_instance = ao2_alloc(sizeof(*monitor_instance), sip_monitor_instance_destructor);
- if (!tcptls_session || !(th = ao2_alloc(sizeof(*th), sip_threadinfo_destructor))) {
+ if (!monitor_instance) {
return NULL;
}
- th->alert_pipe[0] = th->alert_pipe[1] = -1;
-
- if (pipe(th->alert_pipe) == -1) {
- ao2_t_ref(th, -1, "Failed to open alert pipe on sip_threadinfo");
- ast_log(LOG_ERROR, "Could not create sip alert pipe in tcptls thread, error %s\n", strerror(errno));
+ if (ast_string_field_init(monitor_instance, 256)) {
+ ao2_ref(monitor_instance, -1);
return NULL;
}
- ao2_t_ref(tcptls_session, +1, "tcptls_session ref for sip_threadinfo object");
- th->tcptls_session = tcptls_session;
- th->type = transport ? transport : (tcptls_session->ssl ? SIP_TRANSPORT_TLS: SIP_TRANSPORT_TCP);
- ao2_t_link(threadt, th, "Adding new tcptls helper thread");
- ao2_t_ref(th, -1, "Decrementing threadinfo ref from alloc, only table ref remains");
- return th;
+
+ ast_string_field_set(monitor_instance, subscribe_uri, subscribe_uri);
+ ast_string_field_set(monitor_instance, peername, peername);
+ ast_string_field_set(monitor_instance, device_name, device_name);
+ monitor_instance->core_id = core_id;
+ ao2_link(sip_monitor_instances, monitor_instance);
+ return monitor_instance;
}
-/*! \brief used to indicate to a tcptls thread that data is ready to be written */
-static int sip_tcptls_write(struct ast_tcptls_session_instance *tcptls_session, const void *buf, size_t len)
+static int find_sip_monitor_instance_by_subscription_pvt(void *obj, void *arg, int flags)
{
- int res = len;
- struct sip_threadinfo *th = NULL;
- struct tcptls_packet *packet = NULL;
- struct sip_threadinfo tmp = {
- .tcptls_session = tcptls_session,
- };
- enum sip_tcptls_alert alert = TCPTLS_ALERT_DATA;
+ struct sip_monitor_instance *monitor_instance = obj;
+ return monitor_instance->subscription_pvt == arg ? CMP_MATCH | CMP_STOP : 0;
+}
- if (!tcptls_session) {
- return XMIT_ERROR;
- }
+static int find_sip_monitor_instance_by_suspension_entry(void *obj, void *arg, int flags)
+{
+ struct sip_monitor_instance *monitor_instance = obj;
+ return monitor_instance->suspension_entry == arg ? CMP_MATCH | CMP_STOP : 0;
+}
- ast_mutex_lock(&tcptls_session->lock);
+static int sip_cc_monitor_request_cc(struct ast_cc_monitor *monitor, int *available_timer_id);
+static int sip_cc_monitor_suspend(struct ast_cc_monitor *monitor);
+static int sip_cc_monitor_status_response(struct ast_cc_monitor *monitor, enum ast_device_state devstate);
+static int sip_cc_monitor_unsuspend(struct ast_cc_monitor *monitor);
+static int sip_cc_monitor_cancel_available_timer(struct ast_cc_monitor *monitor, int *sched_id);
+static void sip_cc_monitor_destructor(void *private_data);
- if ((tcptls_session->fd == -1) ||
- !(th = ao2_t_find(threadt, &tmp, OBJ_POINTER, "ao2_find, getting sip_threadinfo in tcp helper thread")) ||
- !(packet = ao2_alloc(sizeof(*packet), tcptls_packet_destructor)) ||
- !(packet->data = ast_str_create(len))) {
- goto tcptls_write_setup_error;
- }
+static struct ast_cc_monitor_callbacks sip_cc_monitor_callbacks = {
+ .type = "SIP",
+ .request_cc = sip_cc_monitor_request_cc,
+ .suspend = sip_cc_monitor_suspend,
+ .status_response = sip_cc_monitor_status_response,
+ .unsuspend = sip_cc_monitor_unsuspend,
+ .cancel_available_timer = sip_cc_monitor_cancel_available_timer,
+ .destructor = sip_cc_monitor_destructor,
+};
- /* goto tcptls_write_error should _NOT_ be used beyond this point */
- ast_str_set(&packet->data, 0, "%s", (char *) buf);
- packet->len = len;
+static int sip_cc_monitor_request_cc(struct ast_cc_monitor *monitor, int *available_timer_id)
+{
+ struct sip_monitor_instance *monitor_instance = monitor->private_data;
+ enum ast_cc_service_type service = monitor->service_offered;
+ int when;
- /* alert tcptls thread handler that there is a packet to be sent.
- * must lock the thread info object to guarantee control of the
- * packet queue */
- ao2_lock(th);
- if (write(th->alert_pipe[1], &alert, sizeof(alert)) == -1) {
- ast_log(LOG_ERROR, "write() to alert pipe failed: %s\n", strerror(errno));
- ao2_t_ref(packet, -1, "could not write to alert pipe, remove packet");
- packet = NULL;
- res = XMIT_ERROR;
- } else { /* it is safe to queue the frame after issuing the alert when we hold the threadinfo lock */
- AST_LIST_INSERT_TAIL(&th->packet_q, packet, entry);
+ if (!monitor_instance) {
+ return -1;
}
- ao2_unlock(th);
-
- ast_mutex_unlock(&tcptls_session->lock);
- ao2_t_ref(th, -1, "In sip_tcptls_write, unref threadinfo object after finding it");
- return res;
-tcptls_write_setup_error:
- if (th) {
- ao2_t_ref(th, -1, "In sip_tcptls_write, unref threadinfo obj, could not create packet");
- }
- if (packet) {
- ao2_t_ref(packet, -1, "could not allocate packet's data");
+ if (!(monitor_instance->subscription_pvt = sip_alloc(NULL, NULL, 0, SIP_SUBSCRIBE, NULL))) {
+ return -1;
}
- ast_mutex_unlock(&tcptls_session->lock);
- return XMIT_ERROR;
+ when = service == AST_CC_CCBS ? ast_get_ccbs_available_timer(monitor->interface->config_params) :
+ ast_get_ccnr_available_timer(monitor->interface->config_params);
+
+ sip_pvt_lock(monitor_instance->subscription_pvt);
+ create_addr(monitor_instance->subscription_pvt, monitor_instance->peername, 0, 1);
+ ast_sip_ouraddrfor(&monitor_instance->subscription_pvt->sa.sin_addr, &monitor_instance->subscription_pvt->ourip, monitor_instance->subscription_pvt);
+ monitor_instance->subscription_pvt->subscribed = CALL_COMPLETION;
+ monitor_instance->subscription_pvt->expiry = when;
+
+ transmit_invite(monitor_instance->subscription_pvt, SIP_SUBSCRIBE, FALSE, 2, monitor_instance->subscribe_uri);
+ sip_pvt_unlock(monitor_instance->subscription_pvt);
+
+ ao2_t_ref(monitor, +1, "Adding a ref to the monitor for the scheduler");
+ *available_timer_id = ast_sched_add(sched, when * 1000, ast_cc_available_timer_expire, monitor);
+ return 0;
}
-/*! \brief SIP TCP connection handler */
-static void *sip_tcp_worker_fn(void *data)
+static int construct_pidf_body(enum sip_cc_publish_state state, char *pidf_body, size_t size, const char *presentity)
{
- struct ast_tcptls_session_instance *tcptls_session = data;
+ struct ast_str *body = ast_str_alloca(size);
+ char tuple_id[32];
- return _sip_tcp_helper_thread(NULL, tcptls_session);
+ generate_random_string(tuple_id, sizeof(tuple_id));
+
+ /* We'll make this a bare-bones pidf body. In state_notify_build_xml, the PIDF
+ * body gets a lot more extra junk that isn't necessary, so we'll leave it out here.
+ */
+ ast_str_append(&body, 0, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ /* XXX The entity attribute is currently set to the peer name associated with the
+ * dialog. This is because we currently only call this function for call-completion
+ * PUBLISH bodies. In such cases, the entity is completely disregarded. For other
+ * event packages, it may be crucial to have a proper URI as the presentity so this
+ * should be revisited as support is expanded.
+ */
+ ast_str_append(&body, 0, "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" entity=\"%s\">\n", presentity);
+ ast_str_append(&body, 0, "<tuple id=\"%s\">\n", tuple_id);
+ ast_str_append(&body, 0, "<status><basic>%s</basic></status>\n", state == CC_OPEN ? "open" : "closed");
+ ast_str_append(&body, 0, "</tuple>\n");
+ ast_str_append(&body, 0, "</presence>\n");
+ ast_copy_string(pidf_body, ast_str_buffer(body), size);
+ return 0;
}
-/*! \brief SIP TCP thread management function
- This function reads from the socket, parses the packet into a request
-*/
-static void *_sip_tcp_helper_thread(struct sip_pvt *pvt, struct ast_tcptls_session_instance *tcptls_session)
+static int sip_cc_monitor_suspend(struct ast_cc_monitor *monitor)
{
- int res, cl;
- struct sip_request req = { 0, } , reqcpy = { 0, };
- struct sip_threadinfo *me = NULL;
- char buf[1024] = "";
- struct pollfd fds[2] = { { 0 }, { 0 }, };
- struct ast_tcptls_session_args *ca = NULL;
+ struct sip_monitor_instance *monitor_instance = monitor->private_data;
+ enum sip_publish_type publish_type;
+ struct cc_epa_entry *cc_entry;
- /* If this is a server session, then the connection has already been setup,
- * simply create the threadinfo object so we can access this thread for writing.
- *
- * if this is a client connection more work must be done.
- * 1. We own the parent session args for a client connection. This pointer needs
- * to be held on to so we can decrement it's ref count on thread destruction.
- * 2. The threadinfo object was created before this thread was launched, however
- * it must be found within the threadt table.
- * 3. Last, the tcptls_session must be started.
- */
- if (!tcptls_session->client) {
- if (!(me = sip_threadinfo_create(tcptls_session, tcptls_session->ssl ? SIP_TRANSPORT_TLS : SIP_TRANSPORT_TCP))) {
- goto cleanup;
+ if (!monitor_instance) {
+ return -1;
+ }
+
+ if (!monitor_instance->suspension_entry) {
+ /* We haven't yet allocated the suspension entry, so let's give it a shot */
+ if (!(monitor_instance->suspension_entry = create_epa_entry("call-completion", monitor_instance->peername))) {
+ ast_log(LOG_WARNING, "Unable to allocate sip EPA entry for call-completion\n");
+ ao2_ref(monitor_instance, -1);
+ return -1;
}
- ao2_t_ref(me, +1, "Adding threadinfo ref for tcp_helper_thread");
+ if (!(cc_entry = ast_calloc(1, sizeof(*cc_entry)))) {
+ ast_log(LOG_WARNING, "Unable to allocate space for instance data of EPA entry for call-completion\n");
+ ao2_ref(monitor_instance, -1);
+ return -1;
+ }
+ cc_entry->core_id = monitor->core_id;
+ monitor_instance->suspension_entry->instance_data = cc_entry;
+ publish_type = SIP_PUBLISH_INITIAL;
} else {
- struct sip_threadinfo tmp = {
- .tcptls_session = tcptls_session,
- };
+ publish_type = SIP_PUBLISH_MODIFY;
+ cc_entry = monitor_instance->suspension_entry->instance_data;
+ }
- if ((!(ca = tcptls_session->parent)) ||
- (!(me = ao2_t_find(threadt, &tmp, OBJ_POINTER, "ao2_find, getting sip_threadinfo in tcp helper thread"))) ||
- (!(tcptls_session = ast_tcptls_client_start(tcptls_session)))) {
- goto cleanup;
- }
+ cc_entry->current_state = CC_CLOSED;
+
+ if (ast_strlen_zero(monitor_instance->notify_uri)) {
+ /* If we have no set notify_uri, then what this means is that we have
+ * not received a NOTIFY from this destination stating that he is
+ * currently available.
+ *
+ * This situation can arise when the core calls the suspend callbacks
+ * of multiple destinations. If one of the other destinations aside
+ * from this one notified Asterisk that he is available, then there
+ * is no reason to take any suspension action on this device. Rather,
+ * we should return now and if we receive a NOTIFY while monitoring
+ * is still "suspended" then we can immediately respond with the
+ * proper PUBLISH to let this endpoint know what is going on.
+ */
+ return 0;
}
+ construct_pidf_body(CC_CLOSED, monitor_instance->suspension_entry->body, sizeof(monitor_instance->suspension_entry->body), monitor_instance->peername);
+ return transmit_publish(monitor_instance->suspension_entry, publish_type, monitor_instance->notify_uri);
+}
- me->threadid = pthread_self();
- ast_debug(2, "Starting thread for %s server\n", tcptls_session->ssl ? "SSL" : "TCP");
+static int sip_cc_monitor_status_response(struct ast_cc_monitor *monitor, enum ast_device_state devstate)
+{
+ /* This will never be called because the SIP monitor will never make a status request to
+ * begin with
+ */
+ ast_log(LOG_WARNING, "sip_cc_monitor_status_response called. Something dreadfully wrong must have happened.\n");
+ return 0;
+}
- /* set up pollfd to watch for reads on both the socket and the alert_pipe */
- fds[0].fd = tcptls_session->fd;
- fds[1].fd = me->alert_pipe[0];
- fds[0].events = fds[1].events = POLLIN | POLLPRI;
+static int sip_cc_monitor_unsuspend(struct ast_cc_monitor *monitor)
+{
+ struct sip_monitor_instance *monitor_instance = monitor->private_data;
+ struct cc_epa_entry *cc_entry;
- if (!(req.data = ast_str_create(SIP_MIN_PACKET)))
- goto cleanup;
- if (!(reqcpy.data = ast_str_create(SIP_MIN_PACKET)))
- goto cleanup;
+ if (!monitor_instance) {
+ return -1;
+ }
- for (;;) {
- struct ast_str *str_save;
+ ast_assert(monitor_instance->suspension_entry != NULL);
- res = ast_poll(fds, 2, -1); /* polls for both socket and alert_pipe */
- if (res < 0) {
- ast_debug(2, "SIP %s server :: ast_wait_for_input returned %d\n", tcptls_session->ssl ? "SSL": "TCP", res);
- goto cleanup;
- }
+ cc_entry = monitor_instance->suspension_entry->instance_data;
+ cc_entry->current_state = CC_OPEN;
+ if (ast_strlen_zero(monitor_instance->notify_uri)) {
+ /* This means we are being asked to unsuspend a call leg we never
+ * sent a PUBLISH on. As such, there is no reason to send another
+ * PUBLISH at this point either. We can just return instead.
+ */
+ return 0;
+ }
+ construct_pidf_body(CC_OPEN, monitor_instance->suspension_entry->body, sizeof(monitor_instance->suspension_entry->body), monitor_instance->peername);
+ return transmit_publish(monitor_instance->suspension_entry, SIP_PUBLISH_MODIFY, monitor_instance->notify_uri);
+}
- /* handle the socket event, check for both reads from the socket fd,
- * and writes from alert_pipe fd */
- if (fds[0].revents) { /* there is data on the socket to be read */
+static int sip_cc_monitor_cancel_available_timer(struct ast_cc_monitor *monitor, int *sched_id)
+{
+ if (*sched_id != -1) {
+ AST_SCHED_DEL(sched, *sched_id);
+ ao2_t_ref(monitor, -1, "Removing scheduler's reference to the monitor");
+ }
+ return 0;
+}
- fds[0].revents = 0;
+static void sip_cc_monitor_destructor(void *private_data)
+{
+ struct sip_monitor_instance *monitor_instance = private_data;
+ ao2_unlink(sip_monitor_instances, monitor_instance);
+ ast_module_unref(ast_module_info->self);
+}
- /* clear request structure */
- str_save = req.data;
- memset(&req, 0, sizeof(req));
- req.data = str_save;
- ast_str_reset(req.data);
+static int sip_get_cc_information(struct sip_request *req, char *subscribe_uri, size_t size, enum ast_cc_service_type *service)
+{
+ char *call_info = ast_strdupa(get_header(req, "Call-Info"));
+ char *uri;
+ char *purpose;
+ char *service_str;
+ static const char cc_purpose[] = "purpose=call-completion";
+ static const int cc_purpose_len = sizeof(cc_purpose) - 1;
- str_save = reqcpy.data;
- memset(&reqcpy, 0, sizeof(reqcpy));
- reqcpy.data = str_save;
- ast_str_reset(reqcpy.data);
+ if (ast_strlen_zero(call_info)) {
+ /* No Call-Info present. Definitely no CC offer */
+ return -1;
+ }
- memset(buf, 0, sizeof(buf));
+ uri = strsep(&call_info, ";");
- if (tcptls_session->ssl) {
- set_socket_transport(&req.socket, SIP_TRANSPORT_TLS);
- req.socket.port = htons(ourport_tls);
- } else {
- set_socket_transport(&req.socket, SIP_TRANSPORT_TCP);
- req.socket.port = htons(ourport_tcp);
- }
- req.socket.fd = tcptls_session->fd;
+ while ((purpose = strsep(&call_info, ";"))) {
+ if (!strncmp(purpose, cc_purpose, cc_purpose_len)) {
+ break;
+ }
+ }
+ if (!purpose) {
+ /* We didn't find the appropriate purpose= parameter. Oh well */
+ return -1;
+ }
- /* Read in headers one line at a time */
- while (req.len < 4 || strncmp(REQ_OFFSET_TO_STR(&req, len - 4), "\r\n\r\n", 4)) {
- ast_mutex_lock(&tcptls_session->lock);
- if (!fgets(buf, sizeof(buf), tcptls_session->f)) {
- ast_mutex_unlock(&tcptls_session->lock);
- goto cleanup;
- }
- ast_mutex_unlock(&tcptls_session->lock);
- if (me->stop)
- goto cleanup;
- ast_str_append(&req.data, 0, "%s", buf);
- req.len = req.data->used;
- }
- copy_request(&reqcpy, &req);
- parse_request(&reqcpy);
- /* In order to know how much to read, we need the content-length header */
- if (sscanf(get_header(&reqcpy, "Content-Length"), "%30d", &cl)) {
- while (cl > 0) {
- size_t bytes_read;
- ast_mutex_lock(&tcptls_session->lock);
- if (!(bytes_read = fread(buf, 1, MIN(sizeof(buf) - 1, cl), tcptls_session->f))) {
- ast_mutex_unlock(&tcptls_session->lock);
- goto cleanup;
- }
- buf[bytes_read] = '\0';
- ast_mutex_unlock(&tcptls_session->lock);
- if (me->stop)
- goto cleanup;
- cl -= strlen(buf);
- ast_str_append(&req.data, 0, "%s", buf);
- req.len = req.data->used;
- }
- }
- /*! \todo XXX If there's no Content-Length or if the content-length and what
- we receive is not the same - we should generate an error */
-
- req.socket.tcptls_session = tcptls_session;
- handle_request_do(&req, &tcptls_session->remote_address);
+ /* Okay, call-completion has been offered. Let's figure out what type of service this is */
+ while ((service_str = strsep(&call_info, ";"))) {
+ if (!strncmp(service_str, "m=", 2)) {
+ break;
}
+ }
+ if (!service_str) {
+ /* So they didn't offer a particular service, We'll just go with CCBS since it really
+ * doesn't matter anyway
+ */
+ service_str = "BS";
+ } else {
+ /* We already determined that there is an "m=" so no need to check
+ * the result of this strsep
+ */
+ strsep(&service_str, "=");
+ }
- if (fds[1].revents) { /* alert_pipe indicates there is data in the send queue to be sent */
- enum sip_tcptls_alert alert;
- struct tcptls_packet *packet;
-
- fds[1].revents = 0;
-
- if (read(me->alert_pipe[0], &alert, sizeof(alert)) == -1) {
- ast_log(LOG_ERROR, "read() failed: %s\n", strerror(errno));
- continue;
- }
+ if ((*service = service_string_to_service_type(service_str)) == AST_CC_NONE) {
+ /* Invalid service offered */
+ return -1;
+ }
- switch (alert) {
- case TCPTLS_ALERT_STOP:
- goto cleanup;
- case TCPTLS_ALERT_DATA:
- ao2_lock(me);
- if (!(packet = AST_LIST_REMOVE_HEAD(&me->packet_q, entry))) {
- ast_log(LOG_WARNING, "TCPTLS thread alert_pipe indicated packet should be sent, but frame_q is empty");
- } else if (ast_tcptls_server_write(tcptls_session, ast_str_buffer(packet->data), packet->len) == -1) {
- ast_log(LOG_WARNING, "Failure to write to tcp/tls socket\n");
- }
+ ast_copy_string(subscribe_uri, get_in_brackets(uri), size);
- if (packet) {
- ao2_t_ref(packet, -1, "tcptls packet sent, this is no longer needed");
- }
- ao2_unlock(me);
- break;
- default:
- ast_log(LOG_ERROR, "Unknown tcptls thread alert '%d'\n", alert);
- }
- }
- }
+ return 0;
+}
- ast_debug(2, "Shutting down thread for %s server\n", tcptls_session->ssl ? "SSL" : "TCP");
+/*
+ * \brief Determine what, if any, CC has been offered and queue a CC frame if possible
+ *
+ * After taking care of some formalities to be sure that this call is eligible for CC,
+ * we first try to see if we can make use of native CC. We grab the information from
+ * the passed-in sip_request (which is always a response to an INVITE). If we can
+ * use native CC monitoring for the call, then so be it.
+ *
+ * If native cc monitoring is not possible or not supported, then we will instead attempt
+ * to use generic monitoring. Falling back to generic from a failed attempt at using native
+ * monitoring will only work if the monitor policy of the endpoint is "always"
+ *
+ * \param pvt The current dialog. Contains CC parameters for the endpoint
+ * \param req The response to the INVITE we want to inspect
+ * \param service The service to use if generic monitoring is to be used. For native
+ * monitoring, we get the service from the SIP response itself
+ */
+static void sip_handle_cc(struct sip_pvt *pvt, struct sip_request *req, enum ast_cc_service_type service)
+{
+ enum ast_cc_monitor_policies monitor_policy = ast_get_cc_monitor_policy(pvt->cc_params);
+ int core_id;
+ char interface_name[AST_CHANNEL_NAME];
-cleanup:
- if (me) {
- ao2_t_unlink(threadt, me, "Removing tcptls helper thread, thread is closing");
- ao2_t_ref(me, -1, "Removing tcp_helper_threads threadinfo ref");
- }
- if (reqcpy.data) {
- ast_free(reqcpy.data);
+ if (monitor_policy == AST_CC_MONITOR_NEVER) {
+ /* Don't bother, just return */
+ return;
}
- if (req.data) {
- ast_free(req.data);
- req.data = NULL;
+ if ((core_id = ast_cc_get_current_core_id(pvt->owner)) == -1) {
+ /* For some reason, CC is invalid, so don't try it! */
+ return;
}
- /* if client, we own the parent session arguments and must decrement ref */
- if (ca) {
- ao2_t_ref(ca, -1, "closing tcptls thread, getting rid of client tcptls_session arguments");
- }
+ ast_channel_get_device_name(pvt->owner, interface_name, sizeof(interface_name));
- if (tcptls_session) {
- ast_mutex_lock(&tcptls_session->lock);
- if (tcptls_session->f) {
- fclose(tcptls_session->f);
- tcptls_session->f = NULL;
+ if (monitor_policy == AST_CC_MONITOR_ALWAYS || monitor_policy == AST_CC_MONITOR_NATIVE) {
+ char subscribe_uri[SIPBUFSIZE];
+ char device_name[AST_CHANNEL_NAME];
+ enum ast_cc_service_type offered_service;
+ struct sip_monitor_instance *monitor_instance;
+ if (sip_get_cc_information(req, subscribe_uri, sizeof(subscribe_uri), &offered_service)) {
+ /* If CC isn't being offered to us, or for some reason the CC offer is
+ * not formatted correctly, then it may still be possible to use generic
+ * call completion since the monitor policy may be "always"
+ */
+ goto generic;
}
- if (tcptls_session->fd != -1) {
- close(tcptls_session->fd);
- tcptls_session->fd = -1;
+ ast_channel_get_device_name(pvt->owner, device_name, sizeof(device_name));
+ if (!(monitor_instance = sip_monitor_instance_init(core_id, subscribe_uri, pvt->peername, device_name))) {
+ /* Same deal. We can try using generic still */
+ goto generic;
}
- tcptls_session->parent = NULL;
- ast_mutex_unlock(&tcptls_session->lock);
+ /* We bump the refcount of chan_sip because once we queue this frame, the CC core
+ * will have a reference to callbacks in this module. We decrement the module
+ * refcount once the monitor destructor is called
+ */
+ ast_module_ref(ast_module_info->self);
+ ast_queue_cc_frame(pvt->owner, "SIP", pvt->dialstring, offered_service, monitor_instance);
+ ao2_ref(monitor_instance, -1);
+ return;
+ }
- ao2_ref(tcptls_session, -1);
- tcptls_session = NULL;
+generic:
+ if (monitor_policy == AST_CC_MONITOR_GENERIC || monitor_policy == AST_CC_MONITOR_ALWAYS) {
+ ast_queue_cc_frame(pvt->owner, AST_CC_GENERIC_MONITOR_TYPE, interface_name, service, NULL);
}
- return NULL;
}
+/*! \brief Working TLS connection configuration */
+static struct ast_tls_config sip_tls_cfg;
-/*!
- * helper functions to unreference various types of objects.
- * By handling them this way, we don't have to declare the
- * destructor on each call, which removes the chance of errors.
- */
-static void *unref_peer(struct sip_peer *peer, char *tag)
+/*! \brief Default TLS connection configuration */
+static struct ast_tls_config default_tls_cfg;
+
+/*! \brief The TCP server definition */
+static struct ast_tcptls_session_args sip_tcp_desc = {
+ .accept_fd = -1,
+ .master = AST_PTHREADT_NULL,
+ .tls_cfg = NULL,
+ .poll_timeout = -1,
+ .name = "SIP TCP server",
+ .accept_fn = ast_tcptls_server_root,
+ .worker_fn = sip_tcp_worker_fn,
+};
+
+/*! \brief The TCP/TLS server definition */
+static struct ast_tcptls_session_args sip_tls_desc = {
+ .accept_fd = -1,
+ .master = AST_PTHREADT_NULL,
+ .tls_cfg = &sip_tls_cfg,
+ .poll_timeout = -1,
+ .name = "SIP TLS server",
+ .accept_fn = ast_tcptls_server_root,
+ .worker_fn = sip_tcp_worker_fn,
+};
+
+/*! \brief Append to SIP dialog history
+ \return Always returns 0 */
+#define append_history(p, event, fmt , args... ) append_history_full(p, "%-15s " fmt, event, ## args)
+
+struct sip_pvt *dialog_ref_debug(struct sip_pvt *p, char *tag, char *file, int line, const char *func)
{
- ao2_t_ref(peer, -1, tag);
- return NULL;
+ if (p)
+#ifdef REF_DEBUG
+ __ao2_ref_debug(p, 1, tag, file, line, func);
+#else
+ ao2_ref(p, 1);
+#endif
+ else
+ ast_log(LOG_ERROR, "Attempt to Ref a null pointer\n");
+ return p;
}
-static struct sip_peer *ref_peer(struct sip_peer *peer, char *tag)
+struct sip_pvt *dialog_unref_debug(struct sip_pvt *p, char *tag, char *file, int line, const char *func)
{
- ao2_t_ref(peer, 1, tag);
- return peer;
+ if (p)
+#ifdef REF_DEBUG
+ __ao2_ref_debug(p, -1, tag, file, line, func);
+#else
+ ao2_ref(p, -1);
+#endif
+ return NULL;
}
-/*! \brief maintain proper refcounts for a sip_pvt's outboundproxy
- *
- * This function sets pvt's outboundproxy pointer to the one referenced
- * by the proxy parameter. Because proxy may be a refcounted object, and
- * because pvt's old outboundproxy may also be a refcounted object, we need
- * to maintain the proper refcounts.
- *
- * \param pvt The sip_pvt for which we wish to set the outboundproxy
- * \param proxy The sip_proxy which we will point pvt towards.
- * \return Returns void
+/*! \brief map from an integer value to a string.
+ * If no match is found, return errorstring
*/
-static void ref_proxy(struct sip_pvt *pvt, struct sip_proxy *proxy)
+static const char *map_x_s(const struct _map_x_s *table, int x, const char *errorstring)
{
- struct sip_proxy *old_obproxy = pvt->outboundproxy;
- /* The sip_cfg.outboundproxy is statically allocated, and so
- * we don't ever need to adjust refcounts for it
- */
- if (proxy && proxy != &sip_cfg.outboundproxy) {
- ao2_ref(proxy, +1);
- }
- pvt->outboundproxy = proxy;
- if (old_obproxy && old_obproxy != &sip_cfg.outboundproxy) {
- ao2_ref(old_obproxy, -1);
- }
+ const struct _map_x_s *cur;
+
+ for (cur = table; cur->s; cur++)
+ if (cur->x == x)
+ return cur->s;
+ return errorstring;
}
-/*!
- * \brief Unlink a dialog from the dialogs container, as well as any other places
- * that it may be currently stored.
- *
- * \note A reference to the dialog must be held before calling this function, and this
- * function does not release that reference.
+/*! \brief map from a string to an integer value, case insensitive.
+ * If no match is found, return errorvalue.
*/
-void *dialog_unlink_all(struct sip_pvt *dialog, int lockowner, int lockdialoglist)
+static int map_s_x(const struct _map_x_s *table, const char *s, int errorvalue)
{
- struct sip_pkt *cp;
-
- dialog_ref(dialog, "Let's bump the count in the unlink so it doesn't accidentally become dead before we are done");
+ const struct _map_x_s *cur;
- ao2_t_unlink(dialogs, dialog, "unlinking dialog via ao2_unlink");
+ for (cur = table; cur->s; cur++)
+ if (!strcasecmp(cur->s, s))
+ return cur->x;
+ return errorvalue;
+}
- /* Unlink us from the owner (channel) if we have one */
- if (dialog->owner) {
- if (lockowner)
- ast_channel_lock(dialog->owner);
- ast_debug(1, "Detaching from channel %s\n", dialog->owner->name);
- dialog->owner->tech_pvt = dialog_unref(dialog->owner->tech_pvt, "resetting channel dialog ptr in unlink_all");
- if (lockowner)
- ast_channel_unlock(dialog->owner);
- }
- if (dialog->registry) {
- if (dialog->registry->call == dialog)
- dialog->registry->call = dialog_unref(dialog->registry->call, "nulling out the registry's call dialog field in unlink_all");
- dialog->registry = registry_unref(dialog->registry, "delete dialog->registry");
- }
- if (dialog->stateid > -1) {
- ast_extension_state_del(dialog->stateid, NULL);
- dialog_unref(dialog, "removing extension_state, should unref the associated dialog ptr that was stored there.");
- dialog->stateid = -1; /* shouldn't we 'zero' this out? */
- }
- /* Remove link from peer to subscription of MWI */
- if (dialog->relatedpeer && dialog->relatedpeer->mwipvt == dialog)
- dialog->relatedpeer->mwipvt = dialog_unref(dialog->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt");
- if (dialog->relatedpeer && dialog->relatedpeer->call == dialog)
- dialog->relatedpeer->call = dialog_unref(dialog->relatedpeer->call, "unset the relatedpeer->call field in tandem with relatedpeer field itself");
+static enum AST_REDIRECTING_REASON sip_reason_str_to_code(const char *text)
+{
+ enum AST_REDIRECTING_REASON ast = AST_REDIRECTING_REASON_UNKNOWN;
+ int i;
- /* remove all current packets in this dialog */
- while((cp = dialog->packets)) {
- dialog->packets = dialog->packets->next;
- AST_SCHED_DEL(sched, cp->retransid);
- dialog_unref(cp->owner, "remove all current packets in this dialog, and the pointer to the dialog too as part of __sip_destroy");
- if (cp->data) {
- ast_free(cp->data);
+ for (i = 0; i < ARRAY_LEN(sip_reason_table); ++i) {
+ if (!strcasecmp(text, sip_reason_table[i].text)) {
+ ast = sip_reason_table[i].code;
+ break;
}
- ast_free(cp);
}
- AST_SCHED_DEL_UNREF(sched, dialog->waitid, dialog_unref(dialog, "when you delete the waitid sched, you should dec the refcount for the stored dialog ptr"));
+ return ast;
+}
- AST_SCHED_DEL_UNREF(sched, dialog->initid, dialog_unref(dialog, "when you delete the initid sched, you should dec the refcount for the stored dialog ptr"));
-
- if (dialog->autokillid > -1)
- AST_SCHED_DEL_UNREF(sched, dialog->autokillid, dialog_unref(dialog, "when you delete the autokillid sched, you should dec the refcount for the stored dialog ptr"));
-
- if (dialog->request_queue_sched_id > -1) {
- AST_SCHED_DEL_UNREF(sched, dialog->request_queue_sched_id, dialog_unref(dialog, "when you delete the request_queue_sched_id sched, you should dec the refcount for the stored dialog ptr"));
- }
-
- AST_SCHED_DEL_UNREF(sched, dialog->provisional_keepalive_sched_id, dialog_unref(dialog, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
-
- if (dialog->t38id > -1) {
- AST_SCHED_DEL_UNREF(sched, dialog->t38id, dialog_unref(dialog, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr"));
+static const char *sip_reason_code_to_str(enum AST_REDIRECTING_REASON code)
+{
+ if (code >= 0 && code < ARRAY_LEN(sip_reason_table)) {
+ return sip_reason_table[code].text;
}
- dialog_unref(dialog, "Let's unbump the count in the unlink so the poor pvt can disappear if it is time");
- return NULL;
+ return "unknown";
}
-void *registry_unref(struct sip_registry *reg, char *tag)
-{
- ast_debug(3, "SIP Registry %s: refcount now %d\n", reg->hostname, reg->refcount - 1);
- ASTOBJ_UNREF(reg, sip_registry_destroy);
- return NULL;
-}
+/*!
+ * \brief generic function for determining if a correct transport is being
+ * used to contact a peer
+ *
+ * this is done as a macro so that the "tmpl" var can be passed either a
+ * sip_request or a sip_peer
+ */
+#define check_request_transport(peer, tmpl) ({ \
+ int ret = 0; \
+ if (peer->socket.type == tmpl->socket.type) \
+ ; \
+ else if (!(peer->transports & tmpl->socket.type)) {\
+ ast_log(LOG_ERROR, \
+ "'%s' is not a valid transport for '%s'. we only use '%s'! ending call.\n", \
+ get_transport(tmpl->socket.type), peer->name, get_transport_list(peer->transports) \
+ ); \
+ ret = 1; \
+ } else if (peer->socket.type & SIP_TRANSPORT_TLS) { \
+ ast_log(LOG_WARNING, \
+ "peer '%s' HAS NOT USED (OR SWITCHED TO) TLS in favor of '%s' (but this was allowed in sip.conf)!\n", \
+ peer->name, get_transport(tmpl->socket.type) \
+ ); \
+ } else { \
+ ast_debug(1, \
+ "peer '%s' has contacted us over %s even though we prefer %s.\n", \
+ peer->name, get_transport(tmpl->socket.type), get_transport(peer->socket.type) \
+ ); \
+ }\
+ (ret); \
+})
-/*! \brief Add object reference to SIP registry */
-static struct sip_registry *registry_addref(struct sip_registry *reg, char *tag)
+/*! \brief
+ * duplicate a list of channel variables, \return the copy.
+ */
+static struct ast_variable *copy_vars(struct ast_variable *src)
{
- ast_debug(3, "SIP Registry %s: refcount now %d\n", reg->hostname, reg->refcount + 1);
- return ASTOBJ_REF(reg); /* Add pointer to registry in packet */
-}
-
-/*! \brief Interface structure with callbacks used to connect to UDPTL module*/
-static struct ast_udptl_protocol sip_udptl = {
- type: "SIP",
- get_udptl_info: sip_get_udptl_peer,
- set_udptl_peer: sip_set_udptl_peer,
-};
-
-static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
- __attribute__((format(printf, 2, 3)));
-
+ struct ast_variable *res = NULL, *tmp, *v = NULL;
-/*! \brief Convert transfer status to string */
-static const char *referstatus2str(enum referstatus rstatus)
-{
- return map_x_s(referstatusstrings, rstatus, "");
+ for (v = src ; v ; v = v->next) {
+ if ((tmp = ast_variable_new(v->name, v->value, v->file))) {
+ tmp->next = res;
+ res = tmp;
+ }
+ }
+ return res;
}
-static inline void pvt_set_needdestroy(struct sip_pvt *pvt, const char *reason)
+static void tcptls_packet_destructor(void *obj)
{
- append_history(pvt, "NeedDestroy", "Setting needdestroy because %s", reason);
- pvt->needdestroy = 1;
-}
+ struct tcptls_packet *packet = obj;
-/*! \brief Initialize the initital request packet in the pvt structure.
- This packet is used for creating replies and future requests in
- a dialog */
-static void initialize_initreq(struct sip_pvt *p, struct sip_request *req)
-{
- if (p->initreq.headers)
- ast_debug(1, "Initializing already initialized SIP dialog %s (presumably reinvite)\n", p->callid);
- else
- ast_debug(1, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid);
- /* Use this as the basis */
- copy_request(&p->initreq, req);
- parse_request(&p->initreq);
- if (req->debug)
- ast_verbose("Initreq: %d headers, %d lines\n", p->initreq.headers, p->initreq.lines);
+ ast_free(packet->data);
}
-/*! \brief Encapsulate setting of SIP_ALREADYGONE to be able to trace it with debugging */
-static void sip_alreadygone(struct sip_pvt *dialog)
+static void sip_tcptls_client_args_destructor(void *obj)
{
- ast_debug(3, "Setting SIP_ALREADYGONE on dialog %s\n", dialog->callid);
- dialog->alreadygone = 1;
+ struct ast_tcptls_session_args *args = obj;
+ if (args->tls_cfg) {
+ ast_free(args->tls_cfg->certfile);
+ ast_free(args->tls_cfg->pvtfile);
+ ast_free(args->tls_cfg->cipher);
+ ast_free(args->tls_cfg->cafile);
+ ast_free(args->tls_cfg->capath);
+ }
+ ast_free(args->tls_cfg);
+ ast_free((char *) args->name);
}
-/*! Resolve DNS srv name or host name in a sip_proxy structure */
-static int proxy_update(struct sip_proxy *proxy)
+static void sip_threadinfo_destructor(void *obj)
{
- /* if it's actually an IP address and not a name,
- there's no need for a managed lookup */
- if (!inet_aton(proxy->name, &proxy->ip.sin_addr)) {
- /* Ok, not an IP address, then let's check if it's a domain or host */
- /* XXX Todo - if we have proxy port, don't do SRV */
- if (ast_get_ip_or_srv(&proxy->ip, proxy->name, sip_cfg.srvlookup ? "_sip._udp" : NULL) < 0) {
- ast_log(LOG_WARNING, "Unable to locate host '%s'\n", proxy->name);
- return FALSE;
- }
+ struct sip_threadinfo *th = obj;
+ struct tcptls_packet *packet;
+ if (th->alert_pipe[1] > -1) {
+ close(th->alert_pipe[0]);
}
- proxy->last_dnsupdate = time(NULL);
- return TRUE;
-}
+ if (th->alert_pipe[1] > -1) {
+ close(th->alert_pipe[1]);
+ }
+ th->alert_pipe[0] = th->alert_pipe[1] = -1;
-/*! \brief converts ascii port to int representation. If no
- * pt buffer is provided or the pt has errors when being converted
- * to an int value, the port provided as the standard is used.
- */
-unsigned int port_str2int(const char *pt, unsigned int standard)
-{
- int port = standard;
- if (ast_strlen_zero(pt) || (sscanf(pt, "%30d", &port) != 1) || (port < 1) || (port > 65535)) {
- port = standard;
+ while ((packet = AST_LIST_REMOVE_HEAD(&th->packet_q, entry))) {
+ ao2_t_ref(packet, -1, "thread destruction, removing packet from frame queue");
}
- return port;
+ if (th->tcptls_session) {
+ ao2_t_ref(th->tcptls_session, -1, "remove tcptls_session for sip_threadinfo object");
+ }
}
-/*! \brief Allocate and initialize sip proxy */
-static struct sip_proxy *proxy_allocate(char *name, char *port, int force)
+/*! \brief creates a sip_threadinfo object and links it into the threadt table. */
+static struct sip_threadinfo *sip_threadinfo_create(struct ast_tcptls_session_instance *tcptls_session, int transport)
{
- struct sip_proxy *proxy;
+ struct sip_threadinfo *th;
- if (ast_strlen_zero(name)) {
+ if (!tcptls_session || !(th = ao2_alloc(sizeof(*th), sip_threadinfo_destructor))) {
return NULL;
}
- proxy = ao2_alloc(sizeof(*proxy), NULL);
- if (!proxy)
- return NULL;
- proxy->force = force;
- ast_copy_string(proxy->name, name, sizeof(proxy->name));
- proxy->ip.sin_port = htons(port_str2int(port, STANDARD_SIP_PORT));
- proxy_update(proxy);
- return proxy;
-}
+ th->alert_pipe[0] = th->alert_pipe[1] = -1;
-/*! \brief Get default outbound proxy or global proxy */
-static struct sip_proxy *obproxy_get(struct sip_pvt *dialog, struct sip_peer *peer)
-{
- if (peer && peer->outboundproxy) {
- if (sipdebug)
- ast_debug(1, "OBPROXY: Applying peer OBproxy to this call\n");
- append_history(dialog, "OBproxy", "Using peer obproxy %s", peer->outboundproxy->name);
- return peer->outboundproxy;
- }
- if (sip_cfg.outboundproxy.name[0]) {
- if (sipdebug)
- ast_debug(1, "OBPROXY: Applying global OBproxy to this call\n");
- append_history(dialog, "OBproxy", "Using global obproxy %s", sip_cfg.outboundproxy.name);
- return &sip_cfg.outboundproxy;
+ if (pipe(th->alert_pipe) == -1) {
+ ao2_t_ref(th, -1, "Failed to open alert pipe on sip_threadinfo");
+ ast_log(LOG_ERROR, "Could not create sip alert pipe in tcptls thread, error %s\n", strerror(errno));
+ return NULL;
}
- if (sipdebug)
- ast_debug(1, "OBPROXY: Not applying OBproxy to this call\n");
- return NULL;
+ ao2_t_ref(tcptls_session, +1, "tcptls_session ref for sip_threadinfo object");
+ th->tcptls_session = tcptls_session;
+ th->type = transport ? transport : (tcptls_session->ssl ? SIP_TRANSPORT_TLS: SIP_TRANSPORT_TCP);
+ ao2_t_link(threadt, th, "Adding new tcptls helper thread");
+ ao2_t_ref(th, -1, "Decrementing threadinfo ref from alloc, only table ref remains");
+ return th;
}
-/*! \brief returns true if 'name' (with optional trailing whitespace)
- * matches the sip method 'id'.
- * Strictly speaking, SIP methods are case SENSITIVE, but we do
- * a case-insensitive comparison to be more tolerant.
- * following Jon Postel's rule: Be gentle in what you accept, strict with what you send
- */
-static int method_match(enum sipmethod id, const char *name)
+/*! \brief used to indicate to a tcptls thread that data is ready to be written */
+static int sip_tcptls_write(struct ast_tcptls_session_instance *tcptls_session, const void *buf, size_t len)
{
- int len = strlen(sip_methods[id].text);
- int l_name = name ? strlen(name) : 0;
- /* true if the string is long enough, and ends with whitespace, and matches */
- return (l_name >= len && name[len] < 33 &&
- !strncasecmp(sip_methods[id].text, name, len));
-}
+ int res = len;
+ struct sip_threadinfo *th = NULL;
+ struct tcptls_packet *packet = NULL;
+ struct sip_threadinfo tmp = {
+ .tcptls_session = tcptls_session,
+ };
+ enum sip_tcptls_alert alert = TCPTLS_ALERT_DATA;
-/*! \brief find_sip_method: Find SIP method from header */
-static int find_sip_method(const char *msg)
-{
- int i, res = 0;
-
- if (ast_strlen_zero(msg))
- return 0;
- for (i = 1; i < ARRAY_LEN(sip_methods) && !res; i++) {
- if (method_match(i, msg))
- res = sip_methods[i].id;
+ if (!tcptls_session) {
+ return XMIT_ERROR;
}
- return res;
-}
-
-/*! \brief Parse supported header in incoming packet */
-static unsigned int parse_sip_options(struct sip_pvt *pvt, const char *supported)
-{
- char *next, *sep;
- char *temp;
- unsigned int profile = 0;
- int i, found;
- if (ast_strlen_zero(supported) )
- return 0;
- temp = ast_strdupa(supported);
+ ast_mutex_lock(&tcptls_session->lock);
- if (sipdebug)
- ast_debug(3, "Begin: parsing SIP \"Supported: %s\"\n", supported);
-
- for (next = temp; next; next = sep) {
- found = FALSE;
- if ( (sep = strchr(next, ',')) != NULL)
- *sep++ = '\0';
- next = ast_skip_blanks(next);
- if (sipdebug)
- ast_debug(3, "Found SIP option: -%s-\n", next);
- for (i = 0; i < ARRAY_LEN(sip_options); i++) {
- if (!strcasecmp(next, sip_options[i].text)) {
- profile |= sip_options[i].id;
- found = TRUE;
- if (sipdebug)
- ast_debug(3, "Matched SIP option: %s\n", next);
- break;
- }
- }
+ if ((tcptls_session->fd == -1) ||
+ !(th = ao2_t_find(threadt, &tmp, OBJ_POINTER, "ao2_find, getting sip_threadinfo in tcp helper thread")) ||
+ !(packet = ao2_alloc(sizeof(*packet), tcptls_packet_destructor)) ||
+ !(packet->data = ast_str_create(len))) {
+ goto tcptls_write_setup_error;
+ }
- /* This function is used to parse both Suported: and Require: headers.
- Let the caller of this function know that an unknown option tag was
- encountered, so that if the UAC requires it then the request can be
- rejected with a 420 response. */
- if (!found)
- profile |= SIP_OPT_UNKNOWN;
+ /* goto tcptls_write_error should _NOT_ be used beyond this point */
+ ast_str_set(&packet->data, 0, "%s", (char *) buf);
+ packet->len = len;
- if (!found && sipdebug) {
- if (!strncasecmp(next, "x-", 2))
- ast_debug(3, "Found private SIP option, not supported: %s\n", next);
- else
- ast_debug(3, "Found no match for SIP option: %s (Please file bug report!)\n", next);
- }
+ /* alert tcptls thread handler that there is a packet to be sent.
+ * must lock the thread info object to guarantee control of the
+ * packet queue */
+ ao2_lock(th);
+ if (write(th->alert_pipe[1], &alert, sizeof(alert)) == -1) {
+ ast_log(LOG_ERROR, "write() to alert pipe failed: %s\n", strerror(errno));
+ ao2_t_ref(packet, -1, "could not write to alert pipe, remove packet");
+ packet = NULL;
+ res = XMIT_ERROR;
+ } else { /* it is safe to queue the frame after issuing the alert when we hold the threadinfo lock */
+ AST_LIST_INSERT_TAIL(&th->packet_q, packet, entry);
}
+ ao2_unlock(th);
- if (pvt)
- pvt->sipoptions = profile;
- return profile;
-}
+ ast_mutex_unlock(&tcptls_session->lock);
+ ao2_t_ref(th, -1, "In sip_tcptls_write, unref threadinfo object after finding it");
+ return res;
-/*! \brief See if we pass debug IP filter */
-static inline int sip_debug_test_addr(const struct sockaddr_in *addr)
-{
- if (!sipdebug)
- return 0;
- if (debugaddr.sin_addr.s_addr) {
- if (((ntohs(debugaddr.sin_port) != 0)
- && (debugaddr.sin_port != addr->sin_port))
- || (debugaddr.sin_addr.s_addr != addr->sin_addr.s_addr))
- return 0;
+tcptls_write_setup_error:
+ if (th) {
+ ao2_t_ref(th, -1, "In sip_tcptls_write, unref threadinfo obj, could not create packet");
}
- return 1;
-}
-
-/*! \brief The real destination address for a write */
-static const struct sockaddr_in *sip_real_dst(const struct sip_pvt *p)
-{
- if (p->outboundproxy)
- return &p->outboundproxy->ip;
+ if (packet) {
+ ao2_t_ref(packet, -1, "could not allocate packet's data");
+ }
+ ast_mutex_unlock(&tcptls_session->lock);
- return ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) || ast_test_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT) ? &p->recv : &p->sa;
+ return XMIT_ERROR;
}
-/*! \brief Display SIP nat mode */
-static const char *sip_nat_mode(const struct sip_pvt *p)
+/*! \brief SIP TCP connection handler */
+static void *sip_tcp_worker_fn(void *data)
{
- return ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) ? "NAT" : "no NAT";
-}
+ struct ast_tcptls_session_instance *tcptls_session = data;
-/*! \brief Test PVT for debugging output */
-static inline int sip_debug_test_pvt(struct sip_pvt *p)
-{
- if (!sipdebug)
- return 0;
- return sip_debug_test_addr(sip_real_dst(p));
+ return _sip_tcp_helper_thread(NULL, tcptls_session);
}
-/*! \brief Return int representing a bit field of transport types found in const char *transport */
-static int get_transport_str2enum(const char *transport)
+/*! \brief SIP TCP thread management function
+ This function reads from the socket, parses the packet into a request
+*/
+static void *_sip_tcp_helper_thread(struct sip_pvt *pvt, struct ast_tcptls_session_instance *tcptls_session)
{
- int res = 0;
+ int res, cl;
+ struct sip_request req = { 0, } , reqcpy = { 0, };
+ struct sip_threadinfo *me = NULL;
+ char buf[1024] = "";
+ struct pollfd fds[2] = { { 0 }, { 0 }, };
+ struct ast_tcptls_session_args *ca = NULL;
- if (ast_strlen_zero(transport)) {
- return res;
- }
+ /* If this is a server session, then the connection has already been setup,
+ * simply create the threadinfo object so we can access this thread for writing.
+ *
+ * if this is a client connection more work must be done.
+ * 1. We own the parent session args for a client connection. This pointer needs
+ * to be held on to so we can decrement it's ref count on thread destruction.
+ * 2. The threadinfo object was created before this thread was launched, however
+ * it must be found within the threadt table.
+ * 3. Last, the tcptls_session must be started.
+ */
+ if (!tcptls_session->client) {
+ if (!(me = sip_threadinfo_create(tcptls_session, tcptls_session->ssl ? SIP_TRANSPORT_TLS : SIP_TRANSPORT_TCP))) {
+ goto cleanup;
+ }
+ ao2_t_ref(me, +1, "Adding threadinfo ref for tcp_helper_thread");
+ } else {
+ struct sip_threadinfo tmp = {
+ .tcptls_session = tcptls_session,
+ };
- if (!strcasecmp(transport, "udp")) {
- res |= SIP_TRANSPORT_UDP;
- }
- if (!strcasecmp(transport, "tcp")) {
- res |= SIP_TRANSPORT_TCP;
- }
- if (!strcasecmp(transport, "tls")) {
- res |= SIP_TRANSPORT_TLS;
+ if ((!(ca = tcptls_session->parent)) ||
+ (!(me = ao2_t_find(threadt, &tmp, OBJ_POINTER, "ao2_find, getting sip_threadinfo in tcp helper thread"))) ||
+ (!(tcptls_session = ast_tcptls_client_start(tcptls_session)))) {
+ goto cleanup;
+ }
}
- return res;
-}
+ me->threadid = pthread_self();
+ ast_debug(2, "Starting thread for %s server\n", tcptls_session->ssl ? "SSL" : "TCP");
-/*! \brief Return configuration of transports for a device */
-static inline const char *get_transport_list(unsigned int transports) {
- switch (transports) {
- case SIP_TRANSPORT_UDP:
- return "UDP";
- case SIP_TRANSPORT_TCP:
- return "TCP";
- case SIP_TRANSPORT_TLS:
- return "TLS";
- case SIP_TRANSPORT_UDP | SIP_TRANSPORT_TCP:
- return "TCP,UDP";
- case SIP_TRANSPORT_UDP | SIP_TRANSPORT_TLS:
- return "TLS,UDP";
- case SIP_TRANSPORT_TCP | SIP_TRANSPORT_TLS:
- return "TLS,TCP";
- default:
- return transports ?
- "TLS,TCP,UDP" : "UNKNOWN";
- }
-}
+ /* set up pollfd to watch for reads on both the socket and the alert_pipe */
+ fds[0].fd = tcptls_session->fd;
+ fds[1].fd = me->alert_pipe[0];
+ fds[0].events = fds[1].events = POLLIN | POLLPRI;
-/*! \brief Return transport as string */
-static inline const char *get_transport(enum sip_transport t)
-{
- switch (t) {
- case SIP_TRANSPORT_UDP:
- return "UDP";
- case SIP_TRANSPORT_TCP:
- return "TCP";
- case SIP_TRANSPORT_TLS:
- return "TLS";
- }
+ if (!(req.data = ast_str_create(SIP_MIN_PACKET)))
+ goto cleanup;
+ if (!(reqcpy.data = ast_str_create(SIP_MIN_PACKET)))
+ goto cleanup;
- return "UNKNOWN";
-}
+ for (;;) {
+ struct ast_str *str_save;
-/*! \brief Return transport of dialog.
- \note this is based on a false assumption. We don't always use the
- outbound proxy for all requests in a dialog. It depends on the
- "force" parameter. The FIRST request is always sent to the ob proxy.
- \todo Fix this function to work correctly
-*/
-static inline const char *get_transport_pvt(struct sip_pvt *p)
-{
- if (p->outboundproxy && p->outboundproxy->transport) {
- set_socket_transport(&p->socket, p->outboundproxy->transport);
- }
+ res = ast_poll(fds, 2, -1); /* polls for both socket and alert_pipe */
+ if (res < 0) {
+ ast_debug(2, "SIP %s server :: ast_wait_for_input returned %d\n", tcptls_session->ssl ? "SSL": "TCP", res);
+ goto cleanup;
+ }
- return get_transport(p->socket.type);
-}
+ /* handle the socket event, check for both reads from the socket fd,
+ * and writes from alert_pipe fd */
+ if (fds[0].revents) { /* there is data on the socket to be read */
-/*! \brief Transmit SIP message
- Sends a SIP request or response on a given socket (in the pvt)
- Called by retrans_pkt, send_request, send_response and
- __sip_reliable_xmit
- \return length of transmitted message, XMIT_ERROR on known network failures -1 on other failures.
-*/
-static int __sip_xmit(struct sip_pvt *p, struct ast_str *data, int len)
-{
- int res = 0;
- const struct sockaddr_in *dst = sip_real_dst(p);
+ fds[0].revents = 0;
- ast_debug(2, "Trying to put '%.11s' onto %s socket destined for %s:%d\n", data->str, get_transport_pvt(p), ast_inet_ntoa(dst->sin_addr), htons(dst->sin_port));
+ /* clear request structure */
+ str_save = req.data;
+ memset(&req, 0, sizeof(req));
+ req.data = str_save;
+ ast_str_reset(req.data);
- if (sip_prepare_socket(p) < 0)
- return XMIT_ERROR;
+ str_save = reqcpy.data;
+ memset(&reqcpy, 0, sizeof(reqcpy));
+ reqcpy.data = str_save;
+ ast_str_reset(reqcpy.data);
- if (p->socket.type == SIP_TRANSPORT_UDP) {
- res = sendto(p->socket.fd, data->str, len, 0, (const struct sockaddr *)dst, sizeof(struct sockaddr_in));
- } else if (p->socket.tcptls_session) {
- res = sip_tcptls_write(p->socket.tcptls_session, data->str, len);
- } else {
- ast_debug(2, "Socket type is TCP but no tcptls_session is present to write to\n");
- return XMIT_ERROR;
- }
+ memset(buf, 0, sizeof(buf));
- if (res == -1) {
- switch (errno) {
- case EBADF: /* Bad file descriptor - seems like this is generated when the host exist, but doesn't accept the UDP packet */
- case EHOSTUNREACH: /* Host can't be reached */
- case ENETDOWN: /* Interface down */
- case ENETUNREACH: /* Network failure */
- case ECONNREFUSED: /* ICMP port unreachable */
- res = XMIT_ERROR; /* Don't bother with trying to transmit again */
- }
- }
- if (res != len)
- ast_log(LOG_WARNING, "sip_xmit of %p (len %d) to %s:%d returned %d: %s\n", data, len, ast_inet_ntoa(dst->sin_addr), ntohs(dst->sin_port), res, strerror(errno));
+ if (tcptls_session->ssl) {
+ set_socket_transport(&req.socket, SIP_TRANSPORT_TLS);
+ req.socket.port = htons(ourport_tls);
+ } else {
+ set_socket_transport(&req.socket, SIP_TRANSPORT_TCP);
+ req.socket.port = htons(ourport_tcp);
+ }
+ req.socket.fd = tcptls_session->fd;
- return res;
-}
+ /* Read in headers one line at a time */
+ while (req.len < 4 || strncmp(REQ_OFFSET_TO_STR(&req, len - 4), "\r\n\r\n", 4)) {
+ ast_mutex_lock(&tcptls_session->lock);
+ if (!fgets(buf, sizeof(buf), tcptls_session->f)) {
+ ast_mutex_unlock(&tcptls_session->lock);
+ goto cleanup;
+ }
+ ast_mutex_unlock(&tcptls_session->lock);
+ if (me->stop)
+ goto cleanup;
+ ast_str_append(&req.data, 0, "%s", buf);
+ req.len = req.data->used;
+ }
+ copy_request(&reqcpy, &req);
+ parse_request(&reqcpy);
+ /* In order to know how much to read, we need the content-length header */
+ if (sscanf(get_header(&reqcpy, "Content-Length"), "%30d", &cl)) {
+ while (cl > 0) {
+ size_t bytes_read;
+ ast_mutex_lock(&tcptls_session->lock);
+ if (!(bytes_read = fread(buf, 1, MIN(sizeof(buf) - 1, cl), tcptls_session->f))) {
+ ast_mutex_unlock(&tcptls_session->lock);
+ goto cleanup;
+ }
+ buf[bytes_read] = '\0';
+ ast_mutex_unlock(&tcptls_session->lock);
+ if (me->stop)
+ goto cleanup;
+ cl -= strlen(buf);
+ ast_str_append(&req.data, 0, "%s", buf);
+ req.len = req.data->used;
+ }
+ }
+ /*! \todo XXX If there's no Content-Length or if the content-length and what
+ we receive is not the same - we should generate an error */
-/*! \brief Build a Via header for a request */
-static void build_via(struct sip_pvt *p)
-{
- /* Work around buggy UNIDEN UIP200 firmware */
- const char *rport = (ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) || ast_test_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT)) ? ";rport" : "";
+ req.socket.tcptls_session = tcptls_session;
+ handle_request_do(&req, &tcptls_session->remote_address);
+ }
- /* z9hG4bK is a magic cookie. See RFC 3261 section 8.1.1.7 */
- snprintf(p->via, sizeof(p->via), "SIP/2.0/%s %s:%d;branch=z9hG4bK%08x%s",
- get_transport_pvt(p),
- ast_inet_ntoa(p->ourip.sin_addr),
- ntohs(p->ourip.sin_port), (int) p->branch, rport);
-}
+ if (fds[1].revents) { /* alert_pipe indicates there is data in the send queue to be sent */
+ enum sip_tcptls_alert alert;
+ struct tcptls_packet *packet;
-/*! \brief NAT fix - decide which IP address to use for Asterisk server?
- *
- * Using the localaddr structure built up with localnet statements in sip.conf
- * apply it to their address to see if we need to substitute our
- * externip or can get away with our internal bindaddr
- * 'us' is always overwritten.
- */
-static void ast_sip_ouraddrfor(struct in_addr *them, struct sockaddr_in *us, struct sip_pvt *p)
-{
- struct sockaddr_in theirs;
- /* Set want_remap to non-zero if we want to remap 'us' to an externally
- * reachable IP address and port. This is done if:
- * 1. we have a localaddr list (containing 'internal' addresses marked
- * as 'deny', so ast_apply_ha() will return AST_SENSE_DENY on them,
- * and AST_SENSE_ALLOW on 'external' ones);
- * 2. either stunaddr or externip is set, so we know what to use as the
- * externally visible address;
- * 3. the remote address, 'them', is external;
- * 4. the address returned by ast_ouraddrfor() is 'internal' (AST_SENSE_DENY
- * when passed to ast_apply_ha() so it does need to be remapped.
- * This fourth condition is checked later.
- */
- int want_remap;
+ fds[1].revents = 0;
- *us = internip; /* starting guess for the internal address */
- /* now ask the system what would it use to talk to 'them' */
- ast_ouraddrfor(them, &us->sin_addr);
- theirs.sin_addr = *them;
+ if (read(me->alert_pipe[0], &alert, sizeof(alert)) == -1) {
+ ast_log(LOG_ERROR, "read() failed: %s\n", strerror(errno));
+ continue;
+ }
- want_remap = localaddr &&
- (externip.sin_addr.s_addr || stunaddr.sin_addr.s_addr) &&
- ast_apply_ha(localaddr, &theirs) == AST_SENSE_ALLOW ;
+ switch (alert) {
+ case TCPTLS_ALERT_STOP:
+ goto cleanup;
+ case TCPTLS_ALERT_DATA:
+ ao2_lock(me);
+ if (!(packet = AST_LIST_REMOVE_HEAD(&me->packet_q, entry))) {
+ ast_log(LOG_WARNING, "TCPTLS thread alert_pipe indicated packet should be sent, but frame_q is empty");
+ } else if (ast_tcptls_server_write(tcptls_session, ast_str_buffer(packet->data), packet->len) == -1) {
+ ast_log(LOG_WARNING, "Failure to write to tcp/tls socket\n");
+ }
- if (want_remap &&
- (!sip_cfg.matchexterniplocally || !ast_apply_ha(localaddr, us)) ) {
- /* if we used externhost or stun, see if it is time to refresh the info */
- if (externexpire && time(NULL) >= externexpire) {
- if (stunaddr.sin_addr.s_addr) {
- ast_stun_request(sipsock, &stunaddr, NULL, &externip);
- } else {
- if (ast_parse_arg(externhost, PARSE_INADDR, &externip))
- ast_log(LOG_NOTICE, "Warning: Re-lookup of '%s' failed!\n", externhost);
- }
- externexpire = time(NULL) + externrefresh;
- }
- if (externip.sin_addr.s_addr) {
- *us = externip;
- switch (p->socket.type) {
- case SIP_TRANSPORT_TCP:
- us->sin_port = htons(externtcpport);
- break;
- case SIP_TRANSPORT_TLS:
- us->sin_port = htons(externtlsport);
+ if (packet) {
+ ao2_t_ref(packet, -1, "tcptls packet sent, this is no longer needed");
+ }
+ ao2_unlock(me);
break;
- case SIP_TRANSPORT_UDP:
- break; /* fall through */
default:
- us->sin_port = htons(STANDARD_SIP_PORT); /* we should never get here */
- }
- }
- else
- ast_log(LOG_WARNING, "stun failed\n");
- ast_debug(1, "Target address %s is not local, substituting externip\n",
- ast_inet_ntoa(*(struct in_addr *)&them->s_addr));
- } else if (p) {
- /* no remapping, but we bind to a specific address, so use it. */
- switch (p->socket.type) {
- case SIP_TRANSPORT_TCP:
- if (sip_tcp_desc.local_address.sin_addr.s_addr) {
- *us = sip_tcp_desc.local_address;
- } else {
- us->sin_port = sip_tcp_desc.local_address.sin_port;
- }
- break;
- case SIP_TRANSPORT_TLS:
- if (sip_tls_desc.local_address.sin_addr.s_addr) {
- *us = sip_tls_desc.local_address;
- } else {
- us->sin_port = sip_tls_desc.local_address.sin_port;
- }
- break;
- case SIP_TRANSPORT_UDP:
- /* fall through on purpose */
- default:
- if (bindaddr.sin_addr.s_addr) {
- *us = bindaddr;
+ ast_log(LOG_ERROR, "Unknown tcptls thread alert '%d'\n", alert);
}
}
- } else if (bindaddr.sin_addr.s_addr) {
- *us = bindaddr;
}
- ast_debug(3, "Setting SIP_TRANSPORT_%s with address %s:%d\n", get_transport(p->socket.type), ast_inet_ntoa(us->sin_addr), ntohs(us->sin_port));
-}
-/*! \brief Append to SIP dialog history with arg list */
-static __attribute__((format(printf, 2, 0))) void append_history_va(struct sip_pvt *p, const char *fmt, va_list ap)
-{
- char buf[80], *c = buf; /* max history length */
- struct sip_history *hist;
- int l;
+ ast_debug(2, "Shutting down thread for %s server\n", tcptls_session->ssl ? "SSL" : "TCP");
- vsnprintf(buf, sizeof(buf), fmt, ap);
- strsep(&c, "\r\n"); /* Trim up everything after \r or \n */
- l = strlen(buf) + 1;
- if (!(hist = ast_calloc(1, sizeof(*hist) + l)))
- return;
- if (!p->history && !(p->history = ast_calloc(1, sizeof(*p->history)))) {
- ast_free(hist);
- return;
+cleanup:
+ if (me) {
+ ao2_t_unlink(threadt, me, "Removing tcptls helper thread, thread is closing");
+ ao2_t_ref(me, -1, "Removing tcp_helper_threads threadinfo ref");
}
- memcpy(hist->event, buf, l);
- if (p->history_entries == MAX_HISTORY_ENTRIES) {
- struct sip_history *oldest;
- oldest = AST_LIST_REMOVE_HEAD(p->history, list);
- p->history_entries--;
- ast_free(oldest);
+ if (reqcpy.data) {
+ ast_free(reqcpy.data);
}
- AST_LIST_INSERT_TAIL(p->history, hist, list);
- p->history_entries++;
-}
-
-/*! \brief Append to SIP dialog history with arg list */
-static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
-{
- va_list ap;
- if (!p)
- return;
+ if (req.data) {
+ ast_free(req.data);
+ req.data = NULL;
+ }
- if (!p->do_history && !recordhistory && !dumphistory)
- return;
+ /* if client, we own the parent session arguments and must decrement ref */
+ if (ca) {
+ ao2_t_ref(ca, -1, "closing tcptls thread, getting rid of client tcptls_session arguments");
+ }
- va_start(ap, fmt);
- append_history_va(p, fmt, ap);
- va_end(ap);
+ if (tcptls_session) {
+ ast_mutex_lock(&tcptls_session->lock);
+ if (tcptls_session->f) {
+ fclose(tcptls_session->f);
+ tcptls_session->f = NULL;
+ }
+ if (tcptls_session->fd != -1) {
+ close(tcptls_session->fd);
+ tcptls_session->fd = -1;
+ }
+ tcptls_session->parent = NULL;
+ ast_mutex_unlock(&tcptls_session->lock);
- return;
+ ao2_ref(tcptls_session, -1);
+ tcptls_session = NULL;
+ }
+ return NULL;
}
-/*! \brief Retransmit SIP message if no answer (Called from scheduler) */
-static int retrans_pkt(const void *data)
+
+/*!
+ * helper functions to unreference various types of objects.
+ * By handling them this way, we don't have to declare the
+ * destructor on each call, which removes the chance of errors.
+ */
+static void *unref_peer(struct sip_peer *peer, char *tag)
{
- struct sip_pkt *pkt = (struct sip_pkt *)data, *prev, *cur = NULL;
- int reschedule = DEFAULT_RETRANS;
- int xmitres = 0;
-
- /* Lock channel PVT */
- sip_pvt_lock(pkt->owner);
+ ao2_t_ref(peer, -1, tag);
+ return NULL;
+}
- if (pkt->retrans < MAX_RETRANS) {
- pkt->retrans++;
- if (!pkt->timer_t1) { /* Re-schedule using timer_a and timer_t1 */
- if (sipdebug)
- ast_debug(4, "SIP TIMER: Not rescheduling id #%d:%s (Method %d) (No timer T1)\n", pkt->retransid, sip_methods[pkt->method].text, pkt->method);
- } else {
- int siptimer_a;
+static struct sip_peer *ref_peer(struct sip_peer *peer, char *tag)
+{
+ ao2_t_ref(peer, 1, tag);
+ return peer;
+}
- if (sipdebug)
- ast_debug(4, "SIP TIMER: Rescheduling retransmission #%d (%d) %s - %d\n", pkt->retransid, pkt->retrans, sip_methods[pkt->method].text, pkt->method);
- if (!pkt->timer_a)
- pkt->timer_a = 2 ;
- else
- pkt->timer_a = 2 * pkt->timer_a;
+/*! \brief maintain proper refcounts for a sip_pvt's outboundproxy
+ *
+ * This function sets pvt's outboundproxy pointer to the one referenced
+ * by the proxy parameter. Because proxy may be a refcounted object, and
+ * because pvt's old outboundproxy may also be a refcounted object, we need
+ * to maintain the proper refcounts.
+ *
+ * \param pvt The sip_pvt for which we wish to set the outboundproxy
+ * \param proxy The sip_proxy which we will point pvt towards.
+ * \return Returns void
+ */
+static void ref_proxy(struct sip_pvt *pvt, struct sip_proxy *proxy)
+{
+ struct sip_proxy *old_obproxy = pvt->outboundproxy;
+ /* The sip_cfg.outboundproxy is statically allocated, and so
+ * we don't ever need to adjust refcounts for it
+ */
+ if (proxy && proxy != &sip_cfg.outboundproxy) {
+ ao2_ref(proxy, +1);
+ }
+ pvt->outboundproxy = proxy;
+ if (old_obproxy && old_obproxy != &sip_cfg.outboundproxy) {
+ ao2_ref(old_obproxy, -1);
+ }
+}
- /* For non-invites, a maximum of 4 secs */
- siptimer_a = pkt->timer_t1 * pkt->timer_a; /* Double each time */
- if (pkt->method != SIP_INVITE && siptimer_a > 4000)
- siptimer_a = 4000;
-
- /* Reschedule re-transmit */
- reschedule = siptimer_a;
- ast_debug(4, "** SIP timers: Rescheduling retransmission %d to %d ms (t1 %d ms (Retrans id #%d)) \n", pkt->retrans +1, siptimer_a, pkt->timer_t1, pkt->retransid);
- }
+/*!
+ * \brief Unlink a dialog from the dialogs container, as well as any other places
+ * that it may be currently stored.
+ *
+ * \note A reference to the dialog must be held before calling this function, and this
+ * function does not release that reference.
+ */
+void *dialog_unlink_all(struct sip_pvt *dialog, int lockowner, int lockdialoglist)
+{
+ struct sip_pkt *cp;
- if (sip_debug_test_pvt(pkt->owner)) {
- const struct sockaddr_in *dst = sip_real_dst(pkt->owner);
- ast_verbose("Retransmitting #%d (%s) to %s:%d:\n%s\n---\n",
- pkt->retrans, sip_nat_mode(pkt->owner),
- ast_inet_ntoa(dst->sin_addr),
- ntohs(dst->sin_port), pkt->data->str);
- }
+ dialog_ref(dialog, "Let's bump the count in the unlink so it doesn't accidentally become dead before we are done");
- append_history(pkt->owner, "ReTx", "%d %s", reschedule, pkt->data->str);
- xmitres = __sip_xmit(pkt->owner, pkt->data, pkt->packetlen);
- sip_pvt_unlock(pkt->owner);
- if (xmitres == XMIT_ERROR)
- ast_log(LOG_WARNING, "Network error on retransmit in dialog %s\n", pkt->owner->callid);
- else
- return reschedule;
- }
- /* Too many retries */
- if (pkt->owner && pkt->method != SIP_OPTIONS && xmitres == 0) {
- if (pkt->is_fatal || sipdebug) /* Tell us if it's critical or if we're debugging */
- ast_log(LOG_WARNING, "Maximum retries exceeded on transmission %s for seqno %d (%s %s) -- See doc/sip-retransmit.txt.\n",
- pkt->owner->callid, pkt->seqno,
- pkt->is_fatal ? "Critical" : "Non-critical", pkt->is_resp ? "Response" : "Request");
- } else if (pkt->method == SIP_OPTIONS && sipdebug) {
- ast_log(LOG_WARNING, "Cancelling retransmit of OPTIONs (call id %s) -- See doc/sip-retransmit.txt.\n", pkt->owner->callid);
+ ao2_t_unlink(dialogs, dialog, "unlinking dialog via ao2_unlink");
+ /* Unlink us from the owner (channel) if we have one */
+ if (dialog->owner) {
+ if (lockowner)
+ ast_channel_lock(dialog->owner);
+ ast_debug(1, "Detaching from channel %s\n", dialog->owner->name);
+ dialog->owner->tech_pvt = dialog_unref(dialog->owner->tech_pvt, "resetting channel dialog ptr in unlink_all");
+ if (lockowner)
+ ast_channel_unlock(dialog->owner);
}
- if (xmitres == XMIT_ERROR) {
- ast_log(LOG_WARNING, "Transmit error :: Cancelling transmission on Call ID %s\n", pkt->owner->callid);
- append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
- } else
- append_history(pkt->owner, "MaxRetries", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
-
- pkt->retransid = -1;
+ if (dialog->registry) {
+ if (dialog->registry->call == dialog)
+ dialog->registry->call = dialog_unref(dialog->registry->call, "nulling out the registry's call dialog field in unlink_all");
+ dialog->registry = registry_unref(dialog->registry, "delete dialog->registry");
+ }
+ if (dialog->stateid > -1) {
+ ast_extension_state_del(dialog->stateid, NULL);
+ dialog_unref(dialog, "removing extension_state, should unref the associated dialog ptr that was stored there.");
+ dialog->stateid = -1; /* shouldn't we 'zero' this out? */
+ }
+ /* Remove link from peer to subscription of MWI */
+ if (dialog->relatedpeer && dialog->relatedpeer->mwipvt == dialog)
+ dialog->relatedpeer->mwipvt = dialog_unref(dialog->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt");
+ if (dialog->relatedpeer && dialog->relatedpeer->call == dialog)
+ dialog->relatedpeer->call = dialog_unref(dialog->relatedpeer->call, "unset the relatedpeer->call field in tandem with relatedpeer field itself");
- if (pkt->is_fatal) {
- while(pkt->owner->owner && ast_channel_trylock(pkt->owner->owner)) {
- sip_pvt_unlock(pkt->owner); /* SIP_PVT, not channel */
- usleep(1);
- sip_pvt_lock(pkt->owner);
+ /* remove all current packets in this dialog */
+ while((cp = dialog->packets)) {
+ dialog->packets = dialog->packets->next;
+ AST_SCHED_DEL(sched, cp->retransid);
+ dialog_unref(cp->owner, "remove all current packets in this dialog, and the pointer to the dialog too as part of __sip_destroy");
+ if (cp->data) {
+ ast_free(cp->data);
}
+ ast_free(cp);
+ }
- if (pkt->owner->owner && !pkt->owner->owner->hangupcause)
- pkt->owner->owner->hangupcause = AST_CAUSE_NO_USER_RESPONSE;
-
- if (pkt->owner->owner) {
- sip_alreadygone(pkt->owner);
- ast_log(LOG_WARNING, "Hanging up call %s - no reply to our critical packet (see doc/sip-retransmit.txt).\n", pkt->owner->callid);
- ast_queue_hangup_with_cause(pkt->owner->owner, AST_CAUSE_PROTOCOL_ERROR);
- ast_channel_unlock(pkt->owner->owner);
- } else {
- /* If no channel owner, destroy now */
+ AST_SCHED_DEL_UNREF(sched, dialog->waitid, dialog_unref(dialog, "when you delete the waitid sched, you should dec the refcount for the stored dialog ptr"));
- /* Let the peerpoke system expire packets when the timer expires for poke_noanswer */
- if (pkt->method != SIP_OPTIONS && pkt->method != SIP_REGISTER) {
- pvt_set_needdestroy(pkt->owner, "no response to critical packet");
- sip_alreadygone(pkt->owner);
- append_history(pkt->owner, "DialogKill", "Killing this failed dialog immediately");
- }
- }
- }
+ AST_SCHED_DEL_UNREF(sched, dialog->initid, dialog_unref(dialog, "when you delete the initid sched, you should dec the refcount for the stored dialog ptr"));
+
+ if (dialog->autokillid > -1)
+ AST_SCHED_DEL_UNREF(sched, dialog->autokillid, dialog_unref(dialog, "when you delete the autokillid sched, you should dec the refcount for the stored dialog ptr"));
- if (pkt->method == SIP_BYE) {
- /* We're not getting answers on SIP BYE's. Tear down the call anyway. */
- if (pkt->owner->owner)
- ast_channel_unlock(pkt->owner->owner);
- append_history(pkt->owner, "ByeFailure", "Remote peer doesn't respond to bye. Destroying call anyway.");
- pvt_set_needdestroy(pkt->owner, "no response to BYE");
+ if (dialog->request_queue_sched_id > -1) {
+ AST_SCHED_DEL_UNREF(sched, dialog->request_queue_sched_id, dialog_unref(dialog, "when you delete the request_queue_sched_id sched, you should dec the refcount for the stored dialog ptr"));
}
- /* Remove the packet */
- for (prev = NULL, cur = pkt->owner->packets; cur; prev = cur, cur = cur->next) {
- if (cur == pkt) {
- UNLINK(cur, pkt->owner->packets, prev);
- sip_pvt_unlock(pkt->owner);
- if (pkt->owner)
- pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now");
- if (pkt->data)
- ast_free(pkt->data);
- pkt->data = NULL;
- ast_free(pkt);
- return 0;
- }
+ AST_SCHED_DEL_UNREF(sched, dialog->provisional_keepalive_sched_id, dialog_unref(dialog, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
+
+ if (dialog->t38id > -1) {
+ AST_SCHED_DEL_UNREF(sched, dialog->t38id, dialog_unref(dialog, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr"));
}
- /* error case */
- ast_log(LOG_WARNING, "Weird, couldn't find packet owner!\n");
- sip_pvt_unlock(pkt->owner);
- return 0;
+
+ dialog_unref(dialog, "Let's unbump the count in the unlink so the poor pvt can disappear if it is time");
+ return NULL;
}
-/*! \brief Transmit packet with retransmits
- \return 0 on success, -1 on failure to allocate packet
-*/
-static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, int seqno, int resp, struct ast_str *data, int len, int fatal, int sipmethod)
+void *registry_unref(struct sip_registry *reg, char *tag)
{
- struct sip_pkt *pkt = NULL;
- int siptimer_a = DEFAULT_RETRANS;
- int xmitres = 0;
- int respid;
+ ast_debug(3, "SIP Registry %s: refcount now %d\n", reg->hostname, reg->refcount - 1);
+ ASTOBJ_UNREF(reg, sip_registry_destroy);
+ return NULL;
+}
- if (sipmethod == SIP_INVITE) {
- /* Note this is a pending invite */
- p->pendinginvite = seqno;
- }
+/*! \brief Add object reference to SIP registry */
+static struct sip_registry *registry_addref(struct sip_registry *reg, char *tag)
+{
+ ast_debug(3, "SIP Registry %s: refcount now %d\n", reg->hostname, reg->refcount + 1);
+ return ASTOBJ_REF(reg); /* Add pointer to registry in packet */
+}
- /* If the transport is something reliable (TCP or TLS) then don't really send this reliably */
- /* I removed the code from retrans_pkt that does the same thing so it doesn't get loaded into the scheduler */
- /*! \todo According to the RFC some packets need to be retransmitted even if its TCP, so this needs to get revisited */
- if (!(p->socket.type & SIP_TRANSPORT_UDP)) {
- xmitres = __sip_xmit(p, data, len); /* Send packet */
- if (xmitres == XMIT_ERROR) { /* Serious network trouble, no need to try again */
- append_history(p, "XmitErr", "%s", fatal ? "(Critical)" : "(Non-critical)");
- return AST_FAILURE;
- } else {
- return AST_SUCCESS;
- }
- }
+/*! \brief Interface structure with callbacks used to connect to UDPTL module*/
+static struct ast_udptl_protocol sip_udptl = {
+ type: "SIP",
+ get_udptl_info: sip_get_udptl_peer,
+ set_udptl_peer: sip_set_udptl_peer,
+};
- if (!(pkt = ast_calloc(1, sizeof(*pkt) + len + 1)))
- return AST_FAILURE;
- /* copy data, add a terminator and save length */
- if (!(pkt->data = ast_str_create(len))) {
- ast_free(pkt);
- return AST_FAILURE;
- }
- ast_str_set(&pkt->data, 0, "%s%s", data->str, "\0");
- pkt->packetlen = len;
- /* copy other parameters from the caller */
- pkt->method = sipmethod;
- pkt->seqno = seqno;
- pkt->is_resp = resp;
- pkt->is_fatal = fatal;
- pkt->owner = dialog_ref(p, "__sip_reliable_xmit: setting pkt->owner");
- pkt->next = p->packets;
- p->packets = pkt; /* Add it to the queue */
- if (resp) {
- /* Parse out the response code */
- if (sscanf(ast_str_buffer(pkt->data), "SIP/2.0 %30u", &respid) == 1) {
- pkt->response_code = respid;
- }
- }
- pkt->timer_t1 = p->timer_t1; /* Set SIP timer T1 */
- pkt->retransid = -1;
- if (pkt->timer_t1)
- siptimer_a = pkt->timer_t1 * 2;
+static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
+ __attribute__((format(printf, 2, 3)));
- /* Schedule retransmission */
- AST_SCHED_REPLACE_VARIABLE(pkt->retransid, sched, siptimer_a, retrans_pkt, pkt, 1);
- if (sipdebug)
- ast_debug(4, "*** SIP TIMER: Initializing retransmit timer on packet: Id #%d\n", pkt->retransid);
- xmitres = __sip_xmit(pkt->owner, pkt->data, pkt->packetlen); /* Send packet */
+/*! \brief Convert transfer status to string */
+static const char *referstatus2str(enum referstatus rstatus)
+{
+ return map_x_s(referstatusstrings, rstatus, "");
+}
- if (xmitres == XMIT_ERROR) { /* Serious network trouble, no need to try again */
- append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
- ast_log(LOG_ERROR, "Serious Network Trouble; __sip_xmit returns error for pkt data\n");
- AST_SCHED_DEL(sched, pkt->retransid);
- p->packets = pkt->next;
- pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now");
- ast_free(pkt->data);
- ast_free(pkt);
- return AST_FAILURE;
- } else {
- return AST_SUCCESS;
- }
+static inline void pvt_set_needdestroy(struct sip_pvt *pvt, const char *reason)
+{
+ append_history(pvt, "NeedDestroy", "Setting needdestroy because %s", reason);
+ pvt->needdestroy = 1;
}
-/*! \brief Kill a SIP dialog (called only by the scheduler)
- * The scheduler has a reference to this dialog when p->autokillid != -1,
- * and we are called using that reference. So if the event is not
- * rescheduled, we need to call dialog_unref().
- */
-static int __sip_autodestruct(const void *data)
+/*! \brief Initialize the initital request packet in the pvt structure.
+ This packet is used for creating replies and future requests in
+ a dialog */
+static void initialize_initreq(struct sip_pvt *p, struct sip_request *req)
{
- struct sip_pvt *p = (struct sip_pvt *)data;
+ if (p->initreq.headers)
+ ast_debug(1, "Initializing already initialized SIP dialog %s (presumably reinvite)\n", p->callid);
+ else
+ ast_debug(1, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid);
+ /* Use this as the basis */
+ copy_request(&p->initreq, req);
+ parse_request(&p->initreq);
+ if (req->debug)
+ ast_verbose("Initreq: %d headers, %d lines\n", p->initreq.headers, p->initreq.lines);
+}
- /* If this is a subscription, tell the phone that we got a timeout */
- if (p->subscribed) {
- transmit_state_notify(p, AST_EXTENSION_DEACTIVATED, 1, TRUE); /* Send last notification */
- p->subscribed = NONE;
- append_history(p, "Subscribestatus", "timeout");
- ast_debug(3, "Re-scheduled destruction of SIP subscription %s\n", p->callid ? p->callid : "<unknown>");
- return 10000; /* Reschedule this destruction so that we know that it's gone */
- }
+/*! \brief Encapsulate setting of SIP_ALREADYGONE to be able to trace it with debugging */
+static void sip_alreadygone(struct sip_pvt *dialog)
+{
+ ast_debug(3, "Setting SIP_ALREADYGONE on dialog %s\n", dialog->callid);
+ dialog->alreadygone = 1;
+}
- /* If there are packets still waiting for delivery, delay the destruction */
- if (p->packets) {
- if (!p->needdestroy) {
- char method_str[31];
- ast_debug(3, "Re-scheduled destruction of SIP call %s\n", p->callid ? p->callid : "<unknown>");
- append_history(p, "ReliableXmit", "timeout");
- if (sscanf(p->lastmsg, "Tx: %30s", method_str) == 1 || sscanf(p->lastmsg, "Rx: %30s", method_str) == 1) {
- if (method_match(SIP_CANCEL, method_str) || method_match(SIP_BYE, method_str)) {
- pvt_set_needdestroy(p, "autodestruct");
- }
- }
- return 10000;
- } else {
- /* They've had their chance to respond. Time to bail */
- __sip_pretend_ack(p);
+/*! Resolve DNS srv name or host name in a sip_proxy structure */
+static int proxy_update(struct sip_proxy *proxy)
+{
+ /* if it's actually an IP address and not a name,
+ there's no need for a managed lookup */
+ if (!inet_aton(proxy->name, &proxy->ip.sin_addr)) {
+ /* Ok, not an IP address, then let's check if it's a domain or host */
+ /* XXX Todo - if we have proxy port, don't do SRV */
+ if (ast_get_ip_or_srv(&proxy->ip, proxy->name, sip_cfg.srvlookup ? "_sip._udp" : NULL) < 0) {
+ ast_log(LOG_WARNING, "Unable to locate host '%s'\n", proxy->name);
+ return FALSE;
}
}
+ proxy->last_dnsupdate = time(NULL);
+ return TRUE;
+}
- if (p->subscribed == MWI_NOTIFICATION) {
- if (p->relatedpeer) {
- p->relatedpeer = unref_peer(p->relatedpeer, "__sip_autodestruct: unref peer p->relatedpeer"); /* Remove link to peer. If it's realtime, make sure it's gone from memory) */
- }
+/*! \brief converts ascii port to int representation. If no
+ * pt buffer is provided or the pt has errors when being converted
+ * to an int value, the port provided as the standard is used.
+ */
+unsigned int port_str2int(const char *pt, unsigned int standard)
+{
+ int port = standard;
+ if (ast_strlen_zero(pt) || (sscanf(pt, "%30d", &port) != 1) || (port < 1) || (port > 65535)) {
+ port = standard;
}
- /* Reset schedule ID */
- p->autokillid = -1;
-
- if (p->owner) {
- ast_log(LOG_WARNING, "Autodestruct on dialog '%s' with owner in place (Method: %s)\n", p->callid, sip_methods[p->method].text);
- ast_queue_hangup_with_cause(p->owner, AST_CAUSE_PROTOCOL_ERROR);
- } else if (p->refer && !p->alreadygone) {
- ast_debug(3, "Finally hanging up channel after transfer: %s\n", p->callid);
- transmit_request_with_auth(p, SIP_BYE, 0, XMIT_RELIABLE, 1);
- append_history(p, "ReferBYE", "Sending BYE on transferer call leg %s", p->callid);
- sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
- } else {
- append_history(p, "AutoDestroy", "%s", p->callid);
- ast_debug(3, "Auto destroying SIP dialog '%s'\n", p->callid);
- dialog_unlink_all(p, TRUE, TRUE); /* once it's unlinked and unrefd everywhere, it'll be freed automagically */
- /* dialog_unref(p, "unref dialog-- no other matching conditions"); -- unlink all now should finish off the dialog's references and free it. */
- /* sip_destroy(p); */ /* Go ahead and destroy dialog. All attempts to recover is done */
- /* sip_destroy also absorbs the reference */
- }
- dialog_unref(p, "The ref to a dialog passed to this sched callback is going out of scope; unref it.");
- return 0;
+ return port;
}
-/*! \brief Schedule destruction of SIP dialog */
-void sip_scheddestroy(struct sip_pvt *p, int ms)
+/*! \brief Allocate and initialize sip proxy */
+static struct sip_proxy *proxy_allocate(char *name, char *port, int force)
{
- if (ms < 0) {
- if (p->timer_t1 == 0) {
- p->timer_t1 = global_t1; /* Set timer T1 if not set (RFC 3261) */
- }
- if (p->timer_b == 0) {
- p->timer_b = global_timer_b; /* Set timer B if not set (RFC 3261) */
- }
- ms = p->timer_t1 * 64;
+ struct sip_proxy *proxy;
+
+ if (ast_strlen_zero(name)) {
+ return NULL;
}
- if (sip_debug_test_pvt(p))
- ast_verbose("Scheduling destruction of SIP dialog '%s' in %d ms (Method: %s)\n", p->callid, ms, sip_methods[p->method].text);
- if (sip_cancel_destroy(p))
- ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
- if (p->do_history)
- append_history(p, "SchedDestroy", "%d ms", ms);
- p->autokillid = ast_sched_add(sched, ms, __sip_autodestruct, dialog_ref(p, "setting ref as passing into ast_sched_add for __sip_autodestruct"));
+ proxy = ao2_alloc(sizeof(*proxy), NULL);
+ if (!proxy)
+ return NULL;
+ proxy->force = force;
+ ast_copy_string(proxy->name, name, sizeof(proxy->name));
+ proxy->ip.sin_port = htons(port_str2int(port, STANDARD_SIP_PORT));
+ proxy_update(proxy);
+ return proxy;
+}
- if (p->stimer && p->stimer->st_active == TRUE && p->stimer->st_schedid > 0)
- stop_session_timer(p);
+/*! \brief Get default outbound proxy or global proxy */
+static struct sip_proxy *obproxy_get(struct sip_pvt *dialog, struct sip_peer *peer)
+{
+ if (peer && peer->outboundproxy) {
+ if (sipdebug)
+ ast_debug(1, "OBPROXY: Applying peer OBproxy to this call\n");
+ append_history(dialog, "OBproxy", "Using peer obproxy %s", peer->outboundproxy->name);
+ return peer->outboundproxy;
+ }
+ if (sip_cfg.outboundproxy.name[0]) {
+ if (sipdebug)
+ ast_debug(1, "OBPROXY: Applying global OBproxy to this call\n");
+ append_history(dialog, "OBproxy", "Using global obproxy %s", sip_cfg.outboundproxy.name);
+ return &sip_cfg.outboundproxy;
+ }
+ if (sipdebug)
+ ast_debug(1, "OBPROXY: Not applying OBproxy to this call\n");
+ return NULL;
}
-/*! \brief Cancel destruction of SIP dialog.
- * Be careful as this also absorbs the reference - if you call it
- * from within the scheduler, this might be the last reference.
+/*! \brief returns true if 'name' (with optional trailing whitespace)
+ * matches the sip method 'id'.
+ * Strictly speaking, SIP methods are case SENSITIVE, but we do
+ * a case-insensitive comparison to be more tolerant.
+ * following Jon Postel's rule: Be gentle in what you accept, strict with what you send
*/
-int sip_cancel_destroy(struct sip_pvt *p)
+static int method_match(enum sipmethod id, const char *name)
{
- int res = 0;
- if (p->autokillid > -1) {
- int res3;
+ int len = strlen(sip_methods[id].text);
+ int l_name = name ? strlen(name) : 0;
+ /* true if the string is long enough, and ends with whitespace, and matches */
+ return (l_name >= len && name[len] < 33 &&
+ !strncasecmp(sip_methods[id].text, name, len));
+}
- if (!(res3 = ast_sched_del(sched, p->autokillid))) {
- append_history(p, "CancelDestroy", "");
- p->autokillid = -1;
- dialog_unref(p, "dialog unrefd because autokillid is de-sched'd");
- }
+/*! \brief find_sip_method: Find SIP method from header */
+static int find_sip_method(const char *msg)
+{
+ int i, res = 0;
+
+ if (ast_strlen_zero(msg))
+ return 0;
+ for (i = 1; i < ARRAY_LEN(sip_methods) && !res; i++) {
+ if (method_match(i, msg))
+ res = sip_methods[i].id;
}
return res;
}
-/*! \brief Acknowledges receipt of a packet and stops retransmission
- * called with p locked*/
-int __sip_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
+/*! \brief Parse supported header in incoming packet */
+static unsigned int parse_sip_options(struct sip_pvt *pvt, const char *supported)
{
- struct sip_pkt *cur, *prev = NULL;
- const char *msg = "Not Found"; /* used only for debugging */
- int res = FALSE;
+ char *next, *sep;
+ char *temp;
+ unsigned int profile = 0;
+ int i, found;
- /* If we have an outbound proxy for this dialog, then delete it now since
- the rest of the requests in this dialog needs to follow the routing.
- If obforcing is set, we will keep the outbound proxy during the whole
- dialog, regardless of what the SIP rfc says
- */
- if (p->outboundproxy && !p->outboundproxy->force){
- ref_proxy(p, NULL);
- }
+ if (ast_strlen_zero(supported) )
+ return 0;
+ temp = ast_strdupa(supported);
- for (cur = p->packets; cur; prev = cur, cur = cur->next) {
- if (cur->seqno != seqno || cur->is_resp != resp)
- continue;
- if (cur->is_resp || cur->method == sipmethod) {
- res = TRUE;
- msg = "Found";
- if (!resp && (seqno == p->pendinginvite)) {
- ast_debug(1, "Acked pending invite %d\n", p->pendinginvite);
- p->pendinginvite = 0;
- }
- if (cur->retransid > -1) {
- if (sipdebug)
- ast_debug(4, "** SIP TIMER: Cancelling retransmit of packet (reply received) Retransid #%d\n", cur->retransid);
- }
- /* This odd section is designed to thwart a
- * race condition in the packet scheduler. There are
- * two conditions under which deleting the packet from the
- * scheduler can fail.
- *
- * 1. The packet has been removed from the scheduler because retransmission
- * is being attempted. The problem is that if the packet is currently attempting
- * retransmission and we are at this point in the code, then that MUST mean
- * that retrans_pkt is waiting on p's lock. Therefore we will relinquish the
- * lock temporarily to allow retransmission.
- *
- * 2. The packet has reached its maximum number of retransmissions and has
- * been permanently removed from the packet scheduler. If this is the case, then
- * the packet's retransid will be set to -1. The atomicity of the setting and checking
- * of the retransid to -1 is ensured since in both cases p's lock is held.
- */
- while (cur->retransid > -1 && ast_sched_del(sched, cur->retransid)) {
- sip_pvt_unlock(p);
- usleep(1);
- sip_pvt_lock(p);
+ if (sipdebug)
+ ast_debug(3, "Begin: parsing SIP \"Supported: %s\"\n", supported);
+
+ for (next = temp; next; next = sep) {
+ found = FALSE;
+ if ( (sep = strchr(next, ',')) != NULL)
+ *sep++ = '\0';
+ next = ast_skip_blanks(next);
+ if (sipdebug)
+ ast_debug(3, "Found SIP option: -%s-\n", next);
+ for (i = 0; i < ARRAY_LEN(sip_options); i++) {
+ if (!strcasecmp(next, sip_options[i].text)) {
+ profile |= sip_options[i].id;
+ found = TRUE;
+ if (sipdebug)
+ ast_debug(3, "Matched SIP option: %s\n", next);
+ break;
}
- UNLINK(cur, p->packets, prev);
- dialog_unref(cur->owner, "unref pkt cur->owner dialog from sip ack before freeing pkt");
- if (cur->data)
- ast_free(cur->data);
- ast_free(cur);
- break;
}
- }
- ast_debug(1, "Stopping retransmission on '%s' of %s %d: Match %s\n",
- p->callid, resp ? "Response" : "Request", seqno, msg);
- return res;
-}
-/*! \brief Pretend to ack all packets
- * called with p locked */
-void __sip_pretend_ack(struct sip_pvt *p)
-{
- struct sip_pkt *cur = NULL;
+ /* This function is used to parse both Suported: and Require: headers.
+ Let the caller of this function know that an unknown option tag was
+ encountered, so that if the UAC requires it then the request can be
+ rejected with a 420 response. */
+ if (!found)
+ profile |= SIP_OPT_UNKNOWN;
- while (p->packets) {
- int method;
- if (cur == p->packets) {
- ast_log(LOG_WARNING, "Have a packet that doesn't want to give up! %s\n", sip_methods[cur->method].text);
- return;
+ if (!found && sipdebug) {
+ if (!strncasecmp(next, "x-", 2))
+ ast_debug(3, "Found private SIP option, not supported: %s\n", next);
+ else
+ ast_debug(3, "Found no match for SIP option: %s (Please file bug report!)\n", next);
}
- cur = p->packets;
- method = (cur->method) ? cur->method : find_sip_method(cur->data->str);
- __sip_ack(p, cur->seqno, cur->is_resp, method);
}
+
+ if (pvt)
+ pvt->sipoptions = profile;
+ return profile;
}
-/*! \brief Acks receipt of packet, keep it around (used for provisional responses) */
-int __sip_semi_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
+/*! \brief See if we pass debug IP filter */
+static inline int sip_debug_test_addr(const struct sockaddr_in *addr)
{
- struct sip_pkt *cur;
- int res = FALSE;
-
- for (cur = p->packets; cur; cur = cur->next) {
- if (cur->seqno == seqno && cur->is_resp == resp &&
- (cur->is_resp || method_match(sipmethod, cur->data->str))) {
- /* this is our baby */
- if (cur->retransid > -1) {
- if (sipdebug)
- ast_debug(4, "*** SIP TIMER: Cancelling retransmission #%d - %s (got response)\n", cur->retransid, sip_methods[sipmethod].text);
- }
- AST_SCHED_DEL(sched, cur->retransid);
- res = TRUE;
- break;
- }
+ if (!sipdebug)
+ return 0;
+ if (debugaddr.sin_addr.s_addr) {
+ if (((ntohs(debugaddr.sin_port) != 0)
+ && (debugaddr.sin_port != addr->sin_port))
+ || (debugaddr.sin_addr.s_addr != addr->sin_addr.s_addr))
+ return 0;
}
- ast_debug(1, "(Provisional) Stopping retransmission (but retaining packet) on '%s' %s %d: %s\n", p->callid, resp ? "Response" : "Request", seqno, res == -1 ? "Not Found" : "Found");
- return res;
+ return 1;
}
+/*! \brief The real destination address for a write */
+static const struct sockaddr_in *sip_real_dst(const struct sip_pvt *p)
+{
+ if (p->outboundproxy)
+ return &p->outboundproxy->ip;
+
+ return ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) || ast_test_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT) ? &p->recv : &p->sa;
+}
-/*! \brief Copy SIP request, parse it */
-static void parse_copy(struct sip_request *dst, const struct sip_request *src)
+/*! \brief Display SIP nat mode */
+static const char *sip_nat_mode(const struct sip_pvt *p)
{
- copy_request(dst, src);
- parse_request(dst);
+ return ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) ? "NAT" : "no NAT";
}
-/*! \brief add a blank line if no body */
-static void add_blank(struct sip_request *req)
+/*! \brief Test PVT for debugging output */
+static inline int sip_debug_test_pvt(struct sip_pvt *p)
{
- if (!req->lines) {
- /* Add extra empty return. add_header() reserves 4 bytes so cannot be truncated */
- ast_str_append(&req->data, 0, "\r\n");
- req->len = ast_str_strlen(req->data);
- }
+ if (!sipdebug)
+ return 0;
+ return sip_debug_test_addr(sip_real_dst(p));
}
-static int send_provisional_keepalive_full(struct sip_pvt *pvt, int with_sdp)
+/*! \brief Return int representing a bit field of transport types found in const char *transport */
+static int get_transport_str2enum(const char *transport)
{
- const char *msg = NULL;
+ int res = 0;
- if (!pvt->last_provisional || !strncasecmp(pvt->last_provisional, "100", 3)) {
- msg = "183 Session Progress";
+ if (ast_strlen_zero(transport)) {
+ return res;
}
- if (pvt->invitestate < INV_COMPLETED) {
- if (with_sdp) {
- transmit_response_with_sdp(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq, XMIT_UNRELIABLE, FALSE, FALSE);
- } else {
- transmit_response(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq);
- }
- return PROVIS_KEEPALIVE_TIMEOUT;
+ if (!strcasecmp(transport, "udp")) {
+ res |= SIP_TRANSPORT_UDP;
+ }
+ if (!strcasecmp(transport, "tcp")) {
+ res |= SIP_TRANSPORT_TCP;
+ }
+ if (!strcasecmp(transport, "tls")) {
+ res |= SIP_TRANSPORT_TLS;
}
- return 0;
+ return res;
}
-static int send_provisional_keepalive(const void *data) {
- struct sip_pvt *pvt = (struct sip_pvt *) data;
-
- return send_provisional_keepalive_full(pvt, 0);
+/*! \brief Return configuration of transports for a device */
+static inline const char *get_transport_list(unsigned int transports) {
+ switch (transports) {
+ case SIP_TRANSPORT_UDP:
+ return "UDP";
+ case SIP_TRANSPORT_TCP:
+ return "TCP";
+ case SIP_TRANSPORT_TLS:
+ return "TLS";
+ case SIP_TRANSPORT_UDP | SIP_TRANSPORT_TCP:
+ return "TCP,UDP";
+ case SIP_TRANSPORT_UDP | SIP_TRANSPORT_TLS:
+ return "TLS,UDP";
+ case SIP_TRANSPORT_TCP | SIP_TRANSPORT_TLS:
+ return "TLS,TCP";
+ default:
+ return transports ?
+ "TLS,TCP,UDP" : "UNKNOWN";
+ }
}
-static int send_provisional_keepalive_with_sdp(const void *data) {
- struct sip_pvt *pvt = (void *)data;
+/*! \brief Return transport as string */
+static inline const char *get_transport(enum sip_transport t)
+{
+ switch (t) {
+ case SIP_TRANSPORT_UDP:
+ return "UDP";
+ case SIP_TRANSPORT_TCP:
+ return "TCP";
+ case SIP_TRANSPORT_TLS:
+ return "TLS";
+ }
- return send_provisional_keepalive_full(pvt, 1);
+ return "UNKNOWN";
}
-static void update_provisional_keepalive(struct sip_pvt *pvt, int with_sdp)
+/*! \brief Return transport of dialog.
+ \note this is based on a false assumption. We don't always use the
+ outbound proxy for all requests in a dialog. It depends on the
+ "force" parameter. The FIRST request is always sent to the ob proxy.
+ \todo Fix this function to work correctly
+*/
+static inline const char *get_transport_pvt(struct sip_pvt *p)
{
- AST_SCHED_DEL_UNREF(sched, pvt->provisional_keepalive_sched_id, dialog_unref(pvt, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
+ if (p->outboundproxy && p->outboundproxy->transport) {
+ set_socket_transport(&p->socket, p->outboundproxy->transport);
+ }
- pvt->provisional_keepalive_sched_id = ast_sched_add(sched, PROVIS_KEEPALIVE_TIMEOUT,
- with_sdp ? send_provisional_keepalive_with_sdp : send_provisional_keepalive, dialog_ref(pvt, "Increment refcount to pass dialog pointer to sched callback"));
+ return get_transport(p->socket.type);
}
-/*! \brief Transmit response on SIP request*/
-static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno)
+/*! \brief Transmit SIP message
+ Sends a SIP request or response on a given socket (in the pvt)
+ Called by retrans_pkt, send_request, send_response and
+ __sip_reliable_xmit
+ \return length of transmitted message, XMIT_ERROR on known network failures -1 on other failures.
+*/
+static int __sip_xmit(struct sip_pvt *p, struct ast_str *data, int len)
{
- int res;
+ int res = 0;
+ const struct sockaddr_in *dst = sip_real_dst(p);
- add_blank(req);
- if (sip_debug_test_pvt(p)) {
- const struct sockaddr_in *dst = sip_real_dst(p);
+ ast_debug(2, "Trying to put '%.11s' onto %s socket destined for %s:%d\n", data->str, get_transport_pvt(p), ast_inet_ntoa(dst->sin_addr), htons(dst->sin_port));
- ast_verbose("\n<--- %sTransmitting (%s) to %s:%d --->\n%s\n<------------>\n",
- reliable ? "Reliably " : "", sip_nat_mode(p),
- ast_inet_ntoa(dst->sin_addr),
- ntohs(dst->sin_port), req->data->str);
- }
- if (p->do_history) {
- struct sip_request tmp = { .rlPart1 = 0, };
- parse_copy(&tmp, req);
- append_history(p, reliable ? "TxRespRel" : "TxResp", "%s / %s - %s", tmp.data->str, get_header(&tmp, "CSeq"),
- (tmp.method == SIP_RESPONSE || tmp.method == SIP_UNKNOWN) ? REQ_OFFSET_TO_STR(&tmp, rlPart2) : sip_methods[tmp.method].text);
- ast_free(tmp.data);
+ if (sip_prepare_socket(p) < 0)
+ return XMIT_ERROR;
+
+ if (p->socket.type == SIP_TRANSPORT_UDP) {
+ res = sendto(p->socket.fd, data->str, len, 0, (const struct sockaddr *)dst, sizeof(struct sockaddr_in));
+ } else if (p->socket.tcptls_session) {
+ res = sip_tcptls_write(p->socket.tcptls_session, data->str, len);
+ } else {
+ ast_debug(2, "Socket type is TCP but no tcptls_session is present to write to\n");
+ return XMIT_ERROR;
}
- /* If we are sending a final response to an INVITE, stop retransmitting provisional responses */
- if (p->initreq.method == SIP_INVITE && reliable == XMIT_CRITICAL) {
- AST_SCHED_DEL_UNREF(sched, p->provisional_keepalive_sched_id, dialog_unref(p, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
+ if (res == -1) {
+ switch (errno) {
+ case EBADF: /* Bad file descriptor - seems like this is generated when the host exist, but doesn't accept the UDP packet */
+ case EHOSTUNREACH: /* Host can't be reached */
+ case ENETDOWN: /* Interface down */
+ case ENETUNREACH: /* Network failure */
+ case ECONNREFUSED: /* ICMP port unreachable */
+ res = XMIT_ERROR; /* Don't bother with trying to transmit again */
+ }
}
+ if (res != len)
+ ast_log(LOG_WARNING, "sip_xmit of %p (len %d) to %s:%d returned %d: %s\n", data, len, ast_inet_ntoa(dst->sin_addr), ntohs(dst->sin_port), res, strerror(errno));
- res = (reliable) ?
- __sip_reliable_xmit(p, seqno, 1, req->data, req->len, (reliable == XMIT_CRITICAL), req->method) :
- __sip_xmit(p, req->data, req->len);
- ast_free(req->data);
- req->data = NULL;
- if (res > 0)
- return 0;
return res;
}
-/*! \brief Send SIP Request to the other part of the dialogue
- \return see \ref __sip_xmit
-*/
-static int send_request(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno)
+/*! \brief Build a Via header for a request */
+static void build_via(struct sip_pvt *p)
{
- int res;
-
- /* If we have an outbound proxy, reset peer address
- Only do this once.
- */
- if (p->outboundproxy) {
- p->sa = p->outboundproxy->ip;
- }
+ /* Work around buggy UNIDEN UIP200 firmware */
+ const char *rport = (ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT) || ast_test_flag(&p->flags[0], SIP_NAT_RPORT_PRESENT)) ? ";rport" : "";
- add_blank(req);
- if (sip_debug_test_pvt(p)) {
- if (ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT))
- ast_verbose("%sTransmitting (NAT) to %s:%d:\n%s\n---\n", reliable ? "Reliably " : "", ast_inet_ntoa(p->recv.sin_addr), ntohs(p->recv.sin_port), req->data->str);
- else
- ast_verbose("%sTransmitting (no NAT) to %s:%d:\n%s\n---\n", reliable ? "Reliably " : "", ast_inet_ntoa(p->sa.sin_addr), ntohs(p->sa.sin_port), req->data->str);
- }
- if (p->do_history) {
- struct sip_request tmp = { .rlPart1 = 0, };
- parse_copy(&tmp, req);
- append_history(p, reliable ? "TxReqRel" : "TxReq", "%s / %s - %s", tmp.data->str, get_header(&tmp, "CSeq"), sip_methods[tmp.method].text);
- ast_free(tmp.data);
- }
- res = (reliable) ?
- __sip_reliable_xmit(p, seqno, 0, req->data, req->len, (reliable == XMIT_CRITICAL), req->method) :
- __sip_xmit(p, req->data, req->len);
- if (req->data) {
- ast_free(req->data);
- req->data = NULL;
- }
- return res;
+ /* z9hG4bK is a magic cookie. See RFC 3261 section 8.1.1.7 */
+ snprintf(p->via, sizeof(p->via), "SIP/2.0/%s %s:%d;branch=z9hG4bK%08x%s",
+ get_transport_pvt(p),
+ ast_inet_ntoa(p->ourip.sin_addr),
+ ntohs(p->ourip.sin_port), (int) p->branch, rport);
}
-static void enable_dsp_detect(struct sip_pvt *p)
+/*! \brief NAT fix - decide which IP address to use for Asterisk server?
+ *
+ * Using the localaddr structure built up with localnet statements in sip.conf
+ * apply it to their address to see if we need to substitute our
+ * externip or can get away with our internal bindaddr
+ * 'us' is always overwritten.
+ */
+static void ast_sip_ouraddrfor(struct in_addr *them, struct sockaddr_in *us, struct sip_pvt *p)
{
- int features = 0;
+ struct sockaddr_in theirs;
+ /* Set want_remap to non-zero if we want to remap 'us' to an externally
+ * reachable IP address and port. This is done if:
+ * 1. we have a localaddr list (containing 'internal' addresses marked
+ * as 'deny', so ast_apply_ha() will return AST_SENSE_DENY on them,
+ * and AST_SENSE_ALLOW on 'external' ones);
+ * 2. either stunaddr or externip is set, so we know what to use as the
+ * externally visible address;
+ * 3. the remote address, 'them', is external;
+ * 4. the address returned by ast_ouraddrfor() is 'internal' (AST_SENSE_DENY
+ * when passed to ast_apply_ha() so it does need to be remapped.
+ * This fourth condition is checked later.
+ */
+ int want_remap;
- if (p->dsp) {
- return;
- }
+ *us = internip; /* starting guess for the internal address */
+ /* now ask the system what would it use to talk to 'them' */
+ ast_ouraddrfor(them, &us->sin_addr);
+ theirs.sin_addr = *them;
- if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) ||
- (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) {
- if (!p->rtp || ast_rtp_instance_dtmf_mode_set(p->rtp, AST_RTP_DTMF_MODE_INBAND)) {
- features |= DSP_FEATURE_DIGIT_DETECT;
- }
- }
+ want_remap = localaddr &&
+ (externip.sin_addr.s_addr || stunaddr.sin_addr.s_addr) &&
+ ast_apply_ha(localaddr, &theirs) == AST_SENSE_ALLOW ;
- if (ast_test_flag(&p->flags[1], SIP_PAGE2_FAX_DETECT_CNG)) {
- features |= DSP_FEATURE_FAX_DETECT;
+ if (want_remap &&
+ (!sip_cfg.matchexterniplocally || !ast_apply_ha(localaddr, us)) ) {
+ /* if we used externhost or stun, see if it is time to refresh the info */
+ if (externexpire && time(NULL) >= externexpire) {
+ if (stunaddr.sin_addr.s_addr) {
+ ast_stun_request(sipsock, &stunaddr, NULL, &externip);
+ } else {
+ if (ast_parse_arg(externhost, PARSE_INADDR, &externip))
+ ast_log(LOG_NOTICE, "Warning: Re-lookup of '%s' failed!\n", externhost);
+ }
+ externexpire = time(NULL) + externrefresh;
+ }
+ if (externip.sin_addr.s_addr) {
+ *us = externip;
+ switch (p->socket.type) {
+ case SIP_TRANSPORT_TCP:
+ us->sin_port = htons(externtcpport);
+ break;
+ case SIP_TRANSPORT_TLS:
+ us->sin_port = htons(externtlsport);
+ break;
+ case SIP_TRANSPORT_UDP:
+ break; /* fall through */
+ default:
+ us->sin_port = htons(STANDARD_SIP_PORT); /* we should never get here */
+ }
+ }
+ else
+ ast_log(LOG_WARNING, "stun failed\n");
+ ast_debug(1, "Target address %s is not local, substituting externip\n",
+ ast_inet_ntoa(*(struct in_addr *)&them->s_addr));
+ } else if (p) {
+ /* no remapping, but we bind to a specific address, so use it. */
+ switch (p->socket.type) {
+ case SIP_TRANSPORT_TCP:
+ if (sip_tcp_desc.local_address.sin_addr.s_addr) {
+ *us = sip_tcp_desc.local_address;
+ } else {
+ us->sin_port = sip_tcp_desc.local_address.sin_port;
+ }
+ break;
+ case SIP_TRANSPORT_TLS:
+ if (sip_tls_desc.local_address.sin_addr.s_addr) {
+ *us = sip_tls_desc.local_address;
+ } else {
+ us->sin_port = sip_tls_desc.local_address.sin_port;
+ }
+ break;
+ case SIP_TRANSPORT_UDP:
+ /* fall through on purpose */
+ default:
+ if (bindaddr.sin_addr.s_addr) {
+ *us = bindaddr;
+ }
+ }
+ } else if (bindaddr.sin_addr.s_addr) {
+ *us = bindaddr;
}
+ ast_debug(3, "Setting SIP_TRANSPORT_%s with address %s:%d\n", get_transport(p->socket.type), ast_inet_ntoa(us->sin_addr), ntohs(us->sin_port));
+}
- if (!features) {
- return;
- }
+/*! \brief Append to SIP dialog history with arg list */
+static __attribute__((format(printf, 2, 0))) void append_history_va(struct sip_pvt *p, const char *fmt, va_list ap)
+{
+ char buf[80], *c = buf; /* max history length */
+ struct sip_history *hist;
+ int l;
- if (!(p->dsp = ast_dsp_new())) {
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ strsep(&c, "\r\n"); /* Trim up everything after \r or \n */
+ l = strlen(buf) + 1;
+ if (!(hist = ast_calloc(1, sizeof(*hist) + l)))
+ return;
+ if (!p->history && !(p->history = ast_calloc(1, sizeof(*p->history)))) {
+ ast_free(hist);
return;
}
-
- ast_dsp_set_features(p->dsp, features);
- if (global_relaxdtmf) {
- ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF);
+ memcpy(hist->event, buf, l);
+ if (p->history_entries == MAX_HISTORY_ENTRIES) {
+ struct sip_history *oldest;
+ oldest = AST_LIST_REMOVE_HEAD(p->history, list);
+ p->history_entries--;
+ ast_free(oldest);
}
+ AST_LIST_INSERT_TAIL(p->history, hist, list);
+ p->history_entries++;
}
-static void disable_dsp_detect(struct sip_pvt *p)
+/*! \brief Append to SIP dialog history with arg list */
+static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
{
- if (p->dsp) {
- ast_dsp_free(p->dsp);
- p->dsp = NULL;
- }
-}
+ va_list ap;
-/*! \brief Set an option on a SIP dialog */
-static int sip_setoption(struct ast_channel *chan, int option, void *data, int datalen)
-{
- int res = -1;
- struct sip_pvt *p = chan->tech_pvt;
+ if (!p)
+ return;
- switch (option) {
- case AST_OPTION_FORMAT_READ:
- res = ast_rtp_instance_set_read_format(p->rtp, *(int *) data);
- break;
- case AST_OPTION_FORMAT_WRITE:
- res = ast_rtp_instance_set_write_format(p->rtp, *(int *) data);
- break;
- case AST_OPTION_MAKE_COMPATIBLE:
- res = ast_rtp_instance_make_compatible(chan, p->rtp, (struct ast_channel *) data);
- break;
- case AST_OPTION_DIGIT_DETECT:
- if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) ||
- (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) {
- char *cp = (char *) data;
+ if (!p->do_history && !recordhistory && !dumphistory)
+ return;
- ast_debug(1, "%sabling digit detection on %s\n", *cp ? "En" : "Dis", chan->name);
- if (*cp) {
- enable_dsp_detect(p);
- } else {
- disable_dsp_detect(p);
- }
- res = 0;
- }
- break;
- default:
- break;
- }
+ va_start(ap, fmt);
+ append_history_va(p, fmt, ap);
+ va_end(ap);
- return res;
+ return;
}
-/*! \brief Query an option on a SIP dialog */
-static int sip_queryoption(struct ast_channel *chan, int option, void *data, int *datalen)
+/*! \brief Retransmit SIP message if no answer (Called from scheduler) */
+static int retrans_pkt(const void *data)
{
- int res = -1;
- enum ast_t38_state state = T38_STATE_UNAVAILABLE;
- struct sip_pvt *p = (struct sip_pvt *) chan->tech_pvt;
- char *cp;
-
- switch (option) {
- case AST_OPTION_T38_STATE:
- /* Make sure we got an ast_t38_state enum passed in */
- if (*datalen != sizeof(enum ast_t38_state)) {
- ast_log(LOG_ERROR, "Invalid datalen for AST_OPTION_T38_STATE option. Expected %d, got %d\n", (int)sizeof(enum ast_t38_state), *datalen);
- return -1;
- }
+ struct sip_pkt *pkt = (struct sip_pkt *)data, *prev, *cur = NULL;
+ int reschedule = DEFAULT_RETRANS;
+ int xmitres = 0;
+
+ /* Lock channel PVT */
+ sip_pvt_lock(pkt->owner);
- sip_pvt_lock(p);
+ if (pkt->retrans < MAX_RETRANS) {
+ pkt->retrans++;
+ if (!pkt->timer_t1) { /* Re-schedule using timer_a and timer_t1 */
+ if (sipdebug)
+ ast_debug(4, "SIP TIMER: Not rescheduling id #%d:%s (Method %d) (No timer T1)\n", pkt->retransid, sip_methods[pkt->method].text, pkt->method);
+ } else {
+ int siptimer_a;
- /* Now if T38 support is enabled we need to look and see what the current state is to get what we want to report back */
- if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT)) {
- switch (p->t38.state) {
- case T38_LOCAL_REINVITE:
- case T38_PEER_REINVITE:
- state = T38_STATE_NEGOTIATING;
- break;
- case T38_ENABLED:
- state = T38_STATE_NEGOTIATED;
- break;
- default:
- state = T38_STATE_UNKNOWN;
- }
- }
+ if (sipdebug)
+ ast_debug(4, "SIP TIMER: Rescheduling retransmission #%d (%d) %s - %d\n", pkt->retransid, pkt->retrans, sip_methods[pkt->method].text, pkt->method);
+ if (!pkt->timer_a)
+ pkt->timer_a = 2 ;
+ else
+ pkt->timer_a = 2 * pkt->timer_a;
- sip_pvt_unlock(p);
+ /* For non-invites, a maximum of 4 secs */
+ siptimer_a = pkt->timer_t1 * pkt->timer_a; /* Double each time */
+ if (pkt->method != SIP_INVITE && siptimer_a > 4000)
+ siptimer_a = 4000;
+
+ /* Reschedule re-transmit */
+ reschedule = siptimer_a;
+ ast_debug(4, "** SIP timers: Rescheduling retransmission %d to %d ms (t1 %d ms (Retrans id #%d)) \n", pkt->retrans +1, siptimer_a, pkt->timer_t1, pkt->retransid);
+ }
- *((enum ast_t38_state *) data) = state;
- res = 0;
+ if (sip_debug_test_pvt(pkt->owner)) {
+ const struct sockaddr_in *dst = sip_real_dst(pkt->owner);
+ ast_verbose("Retransmitting #%d (%s) to %s:%d:\n%s\n---\n",
+ pkt->retrans, sip_nat_mode(pkt->owner),
+ ast_inet_ntoa(dst->sin_addr),
+ ntohs(dst->sin_port), pkt->data->str);
+ }
- break;
- case AST_OPTION_DIGIT_DETECT:
- cp = (char *) data;
- *cp = p->dsp ? 1 : 0;
- ast_debug(1, "Reporting digit detection %sabled on %s\n", *cp ? "en" : "dis", chan->name);
- break;
- default:
- break;
+ append_history(pkt->owner, "ReTx", "%d %s", reschedule, pkt->data->str);
+ xmitres = __sip_xmit(pkt->owner, pkt->data, pkt->packetlen);
+ sip_pvt_unlock(pkt->owner);
+ if (xmitres == XMIT_ERROR)
+ ast_log(LOG_WARNING, "Network error on retransmit in dialog %s\n", pkt->owner->callid);
+ else
+ return reschedule;
}
+ /* Too many retries */
+ if (pkt->owner && pkt->method != SIP_OPTIONS && xmitres == 0) {
+ if (pkt->is_fatal || sipdebug) /* Tell us if it's critical or if we're debugging */
+ ast_log(LOG_WARNING, "Maximum retries exceeded on transmission %s for seqno %d (%s %s) -- See doc/sip-retransmit.txt.\n",
+ pkt->owner->callid, pkt->seqno,
+ pkt->is_fatal ? "Critical" : "Non-critical", pkt->is_resp ? "Response" : "Request");
+ } else if (pkt->method == SIP_OPTIONS && sipdebug) {
+ ast_log(LOG_WARNING, "Cancelling retransmit of OPTIONs (call id %s) -- See doc/sip-retransmit.txt.\n", pkt->owner->callid);
- return res;
-}
-
-/*! \brief Locate closing quote in a string, skipping escaped quotes.
- * optionally with a limit on the search.
- * start must be past the first quote.
- */
-const char *find_closing_quote(const char *start, const char *lim)
-{
- char last_char = '\0';
- const char *s;
- for (s = start; *s && s != lim; last_char = *s++) {
- if (*s == '"' && last_char != '\\')
- break;
}
- return s;
-}
-
-/*! \brief Send message with Access-URL header, if this is an HTML URL only! */
-static int sip_sendhtml(struct ast_channel *chan, int subclass, const char *data, int datalen)
-{
- struct sip_pvt *p = chan->tech_pvt;
+ if (xmitres == XMIT_ERROR) {
+ ast_log(LOG_WARNING, "Transmit error :: Cancelling transmission on Call ID %s\n", pkt->owner->callid);
+ append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
+ } else
+ append_history(pkt->owner, "MaxRetries", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
+
+ pkt->retransid = -1;
- if (subclass != AST_HTML_URL)
- return -1;
+ if (pkt->is_fatal) {
+ while(pkt->owner->owner && ast_channel_trylock(pkt->owner->owner)) {
+ sip_pvt_unlock(pkt->owner); /* SIP_PVT, not channel */
+ usleep(1);
+ sip_pvt_lock(pkt->owner);
+ }
- ast_string_field_build(p, url, "<%s>;mode=active", data);
+ if (pkt->owner->owner && !pkt->owner->owner->hangupcause)
+ pkt->owner->owner->hangupcause = AST_CAUSE_NO_USER_RESPONSE;
+
+ if (pkt->owner->owner) {
+ sip_alreadygone(pkt->owner);
+ ast_log(LOG_WARNING, "Hanging up call %s - no reply to our critical packet (see doc/sip-retransmit.txt).\n", pkt->owner->callid);
+ ast_queue_hangup_with_cause(pkt->owner->owner, AST_CAUSE_PROTOCOL_ERROR);
+ ast_channel_unlock(pkt->owner->owner);
+ } else {
+ /* If no channel owner, destroy now */
- if (sip_debug_test_pvt(p))
- ast_debug(1, "Send URL %s, state = %d!\n", data, chan->_state);
+ /* Let the peerpoke system expire packets when the timer expires for poke_noanswer */
+ if (pkt->method != SIP_OPTIONS && pkt->method != SIP_REGISTER) {
+ pvt_set_needdestroy(pkt->owner, "no response to critical packet");
+ sip_alreadygone(pkt->owner);
+ append_history(pkt->owner, "DialogKill", "Killing this failed dialog immediately");
+ }
+ }
+ }
- switch (chan->_state) {
- case AST_STATE_RING:
- transmit_response(p, "100 Trying", &p->initreq);
- break;
- case AST_STATE_RINGING:
- transmit_response(p, "180 Ringing", &p->initreq);
- break;
- case AST_STATE_UP:
- if (!p->pendinginvite) { /* We are up, and have no outstanding invite */
- transmit_reinvite_with_sdp(p, FALSE, FALSE);
- } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
- ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
- }
- break;
- default:
- ast_log(LOG_WARNING, "Don't know how to send URI when state is %d!\n", chan->_state);
+ if (pkt->method == SIP_BYE) {
+ /* We're not getting answers on SIP BYE's. Tear down the call anyway. */
+ if (pkt->owner->owner)
+ ast_channel_unlock(pkt->owner->owner);
+ append_history(pkt->owner, "ByeFailure", "Remote peer doesn't respond to bye. Destroying call anyway.");
+ pvt_set_needdestroy(pkt->owner, "no response to BYE");
}
+ /* Remove the packet */
+ for (prev = NULL, cur = pkt->owner->packets; cur; prev = cur, cur = cur->next) {
+ if (cur == pkt) {
+ UNLINK(cur, pkt->owner->packets, prev);
+ sip_pvt_unlock(pkt->owner);
+ if (pkt->owner)
+ pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now");
+ if (pkt->data)
+ ast_free(pkt->data);
+ pkt->data = NULL;
+ ast_free(pkt);
+ return 0;
+ }
+ }
+ /* error case */
+ ast_log(LOG_WARNING, "Weird, couldn't find packet owner!\n");
+ sip_pvt_unlock(pkt->owner);
return 0;
}
-/*! \brief Deliver SIP call ID for the call */
-static const char *sip_get_callid(struct ast_channel *chan)
-{
- return chan->tech_pvt ? ((struct sip_pvt *) chan->tech_pvt)->callid : "";
-}
-
-/*! \brief Send SIP MESSAGE text within a call
- Called from PBX core sendtext() application */
-static int sip_sendtext(struct ast_channel *ast, const char *text)
+/*! \brief Transmit packet with retransmits
+ \return 0 on success, -1 on failure to allocate packet
+*/
+static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, int seqno, int resp, struct ast_str *data, int len, int fatal, int sipmethod)
{
- struct sip_pvt *dialog = ast->tech_pvt;
- int debug = sip_debug_test_pvt(dialog);
+ struct sip_pkt *pkt = NULL;
+ int siptimer_a = DEFAULT_RETRANS;
+ int xmitres = 0;
+ int respid;
- if (!dialog)
- return -1;
- /* NOT ast_strlen_zero, because a zero-length message is specifically
- * allowed by RFC 3428 (See section 10, Examples) */
- if (!text)
- return 0;
- if(!is_method_allowed(&dialog->allowed_methods, SIP_MESSAGE)) {
- ast_debug(2, "Trying to send MESSAGE to device that does not support it.\n");
- return(0);
+ if (sipmethod == SIP_INVITE) {
+ /* Note this is a pending invite */
+ p->pendinginvite = seqno;
}
- if (debug)
- ast_verbose("Sending text %s on %s\n", text, ast->name);
- transmit_message_with_text(dialog, text);
- return 0;
-}
-/*! \brief Update peer object in realtime storage
- If the Asterisk system name is set in asterisk.conf, we will use
- that name and store that in the "regserver" field in the sippeers
- table to facilitate multi-server setups.
-*/
-static void realtime_update_peer(const char *peername, struct sockaddr_in *sin, const char *defaultuser, const char *fullcontact, const char *useragent, int expirey, unsigned short deprecated_username, int lastms)
-{
- char port[10];
- char ipaddr[INET_ADDRSTRLEN];
- char regseconds[20];
- char *tablename = NULL;
- char str_lastms[20];
-
- const char *sysname = ast_config_AST_SYSTEM_NAME;
- char *syslabel = NULL;
+ /* If the transport is something reliable (TCP or TLS) then don't really send this reliably */
+ /* I removed the code from retrans_pkt that does the same thing so it doesn't get loaded into the scheduler */
+ /*! \todo According to the RFC some packets need to be retransmitted even if its TCP, so this needs to get revisited */
+ if (!(p->socket.type & SIP_TRANSPORT_UDP)) {
+ xmitres = __sip_xmit(p, data, len); /* Send packet */
+ if (xmitres == XMIT_ERROR) { /* Serious network trouble, no need to try again */
+ append_history(p, "XmitErr", "%s", fatal ? "(Critical)" : "(Non-critical)");
+ return AST_FAILURE;
+ } else {
+ return AST_SUCCESS;
+ }
+ }
- time_t nowtime = time(NULL) + expirey;
- const char *fc = fullcontact ? "fullcontact" : NULL;
+ if (!(pkt = ast_calloc(1, sizeof(*pkt) + len + 1)))
+ return AST_FAILURE;
+ /* copy data, add a terminator and save length */
+ if (!(pkt->data = ast_str_create(len))) {
+ ast_free(pkt);
+ return AST_FAILURE;
+ }
+ ast_str_set(&pkt->data, 0, "%s%s", data->str, "\0");
+ pkt->packetlen = len;
+ /* copy other parameters from the caller */
+ pkt->method = sipmethod;
+ pkt->seqno = seqno;
+ pkt->is_resp = resp;
+ pkt->is_fatal = fatal;
+ pkt->owner = dialog_ref(p, "__sip_reliable_xmit: setting pkt->owner");
+ pkt->next = p->packets;
+ p->packets = pkt; /* Add it to the queue */
+ if (resp) {
+ /* Parse out the response code */
+ if (sscanf(ast_str_buffer(pkt->data), "SIP/2.0 %30u", &respid) == 1) {
+ pkt->response_code = respid;
+ }
+ }
+ pkt->timer_t1 = p->timer_t1; /* Set SIP timer T1 */
+ pkt->retransid = -1;
+ if (pkt->timer_t1)
+ siptimer_a = pkt->timer_t1 * 2;
- int realtimeregs = ast_check_realtime("sipregs");
+ /* Schedule retransmission */
+ AST_SCHED_REPLACE_VARIABLE(pkt->retransid, sched, siptimer_a, retrans_pkt, pkt, 1);
+ if (sipdebug)
+ ast_debug(4, "*** SIP TIMER: Initializing retransmit timer on packet: Id #%d\n", pkt->retransid);
- tablename = realtimeregs ? "sipregs" : "sippeers";
-
+ xmitres = __sip_xmit(pkt->owner, pkt->data, pkt->packetlen); /* Send packet */
- snprintf(str_lastms, sizeof(str_lastms), "%d", lastms);
- snprintf(regseconds, sizeof(regseconds), "%d", (int)nowtime); /* Expiration time */
- ast_copy_string(ipaddr, ast_inet_ntoa(sin->sin_addr), sizeof(ipaddr));
- snprintf(port, sizeof(port), "%d", ntohs(sin->sin_port));
-
- if (ast_strlen_zero(sysname)) /* No system name, disable this */
- sysname = NULL;
- else if (sip_cfg.rtsave_sysname)
- syslabel = "regserver";
-
- if (fc) {
- ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
- "port", port, "regseconds", regseconds,
- deprecated_username ? "username" : "defaultuser", defaultuser,
- "useragent", useragent, "lastms", str_lastms,
- fc, fullcontact, syslabel, sysname, SENTINEL); /* note fc and syslabel _can_ be NULL */
+ if (xmitres == XMIT_ERROR) { /* Serious network trouble, no need to try again */
+ append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
+ ast_log(LOG_ERROR, "Serious Network Trouble; __sip_xmit returns error for pkt data\n");
+ AST_SCHED_DEL(sched, pkt->retransid);
+ p->packets = pkt->next;
+ pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now");
+ ast_free(pkt->data);
+ ast_free(pkt);
+ return AST_FAILURE;
} else {
- ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
- "port", port, "regseconds", regseconds,
- "useragent", useragent, "lastms", str_lastms,
- deprecated_username ? "username" : "defaultuser", defaultuser,
- syslabel, sysname, SENTINEL); /* note syslabel _can_ be NULL */
+ return AST_SUCCESS;
}
}
-/*! \brief Automatically add peer extension to dial plan */
-static void register_peer_exten(struct sip_peer *peer, int onoff)
+/*! \brief Kill a SIP dialog (called only by the scheduler)
+ * The scheduler has a reference to this dialog when p->autokillid != -1,
+ * and we are called using that reference. So if the event is not
+ * rescheduled, we need to call dialog_unref().
+ */
+static int __sip_autodestruct(const void *data)
{
- char multi[256];
- char *stringp, *ext, *context;
- struct pbx_find_info q = { .stacklen = 0 };
+ struct sip_pvt *p = (struct sip_pvt *)data;
- /* XXX note that sip_cfg.regcontext is both a global 'enable' flag and
- * the name of the global regexten context, if not specified
- * individually.
- */
- if (ast_strlen_zero(sip_cfg.regcontext))
- return;
+ /* If this is a subscription, tell the phone that we got a timeout */
+ if (p->subscribed && p->subscribed != MWI_NOTIFICATION && p->subscribed != CALL_COMPLETION) {
+ transmit_state_notify(p, AST_EXTENSION_DEACTIVATED, 1, TRUE); /* Send last notification */
+ p->subscribed = NONE;
+ append_history(p, "Subscribestatus", "timeout");
+ ast_debug(3, "Re-scheduled destruction of SIP subscription %s\n", p->callid ? p->callid : "<unknown>");
+ return 10000; /* Reschedule this destruction so that we know that it's gone */
+ }
- ast_copy_string(multi, S_OR(peer->regexten, peer->name), sizeof(multi));
- stringp = multi;
- while ((ext = strsep(&stringp, "&"))) {
- if ((context = strchr(ext, '@'))) {
- *context++ = '\0'; /* split ext@context */
- if (!ast_context_find(context)) {
- ast_log(LOG_WARNING, "Context %s must exist in regcontext= in sip.conf!\n", context);
- continue;
+ /* If there are packets still waiting for delivery, delay the destruction */
+ if (p->packets) {
+ if (!p->needdestroy) {
+ char method_str[31];
+ ast_debug(3, "Re-scheduled destruction of SIP call %s\n", p->callid ? p->callid : "<unknown>");
+ append_history(p, "ReliableXmit", "timeout");
+ if (sscanf(p->lastmsg, "Tx: %30s", method_str) == 1 || sscanf(p->lastmsg, "Rx: %30s", method_str) == 1) {
+ if (method_match(SIP_CANCEL, method_str) || method_match(SIP_BYE, method_str)) {
+ pvt_set_needdestroy(p, "autodestruct");
+ }
}
+ return 10000;
} else {
- context = sip_cfg.regcontext;
- }
- if (onoff) {
- if (!ast_exists_extension(NULL, context, ext, 1, NULL)) {
- ast_add_extension(context, 1, ext, 1, NULL, NULL, "Noop",
- ast_strdup(peer->name), ast_free_ptr, "SIP");
- }
- } else if (pbx_find_extension(NULL, NULL, &q, context, ext, 1, NULL, "", E_MATCH)) {
- ast_context_remove_extension(context, ext, 1, NULL);
+ /* They've had their chance to respond. Time to bail */
+ __sip_pretend_ack(p);
}
}
-}
-
-/*! Destroy mailbox subscriptions */
-static void destroy_mailbox(struct sip_mailbox *mailbox)
-{
- if (mailbox->mailbox)
- ast_free(mailbox->mailbox);
- if (mailbox->context)
- ast_free(mailbox->context);
- if (mailbox->event_sub)
- ast_event_unsubscribe(mailbox->event_sub);
- ast_free(mailbox);
-}
-/*! Destroy all peer-related mailbox subscriptions */
-static void clear_peer_mailboxes(struct sip_peer *peer)
-{
- struct sip_mailbox *mailbox;
+ if (p->subscribed == MWI_NOTIFICATION) {
+ if (p->relatedpeer) {
+ p->relatedpeer = unref_peer(p->relatedpeer, "__sip_autodestruct: unref peer p->relatedpeer"); /* Remove link to peer. If it's realtime, make sure it's gone from memory) */
+ }
+ }
- while ((mailbox = AST_LIST_REMOVE_HEAD(&peer->mailboxes, entry)))
- destroy_mailbox(mailbox);
-}
+ /* Reset schedule ID */
+ p->autokillid = -1;
-static void sip_destroy_peer_fn(void *peer)
-{
- sip_destroy_peer(peer);
+ if (p->owner) {
+ ast_log(LOG_WARNING, "Autodestruct on dialog '%s' with owner in place (Method: %s)\n", p->callid, sip_methods[p->method].text);
+ ast_queue_hangup_with_cause(p->owner, AST_CAUSE_PROTOCOL_ERROR);
+ } else if (p->refer && !p->alreadygone) {
+ ast_debug(3, "Finally hanging up channel after transfer: %s\n", p->callid);
+ transmit_request_with_auth(p, SIP_BYE, 0, XMIT_RELIABLE, 1);
+ append_history(p, "ReferBYE", "Sending BYE on transferer call leg %s", p->callid);
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ } else {
+ append_history(p, "AutoDestroy", "%s", p->callid);
+ ast_debug(3, "Auto destroying SIP dialog '%s'\n", p->callid);
+ dialog_unlink_all(p, TRUE, TRUE); /* once it's unlinked and unrefd everywhere, it'll be freed automagically */
+ /* dialog_unref(p, "unref dialog-- no other matching conditions"); -- unlink all now should finish off the dialog's references and free it. */
+ /* sip_destroy(p); */ /* Go ahead and destroy dialog. All attempts to recover is done */
+ /* sip_destroy also absorbs the reference */
+ }
+ dialog_unref(p, "The ref to a dialog passed to this sched callback is going out of scope; unref it.");
+ return 0;
}
-/*! \brief Destroy peer object from memory */
-static void sip_destroy_peer(struct sip_peer *peer)
+/*! \brief Schedule destruction of SIP dialog */
+void sip_scheddestroy(struct sip_pvt *p, int ms)
{
- ast_debug(3, "Destroying SIP peer %s\n", peer->name);
- if (peer->outboundproxy)
- ao2_ref(peer->outboundproxy, -1);
- peer->outboundproxy = NULL;
-
- /* Delete it, it needs to disappear */
- if (peer->call) {
- dialog_unlink_all(peer->call, TRUE, TRUE);
- peer->call = dialog_unref(peer->call, "peer->call is being unset");
- }
-
-
- if (peer->mwipvt) { /* We have an active subscription, delete it */
- dialog_unlink_all(peer->mwipvt, TRUE, TRUE);
- peer->mwipvt = dialog_unref(peer->mwipvt, "unreffing peer->mwipvt");
- }
-
- if (peer->chanvars) {
- ast_variables_destroy(peer->chanvars);
- peer->chanvars = NULL;
+ if (ms < 0) {
+ if (p->timer_t1 == 0) {
+ p->timer_t1 = global_t1; /* Set timer T1 if not set (RFC 3261) */
+ }
+ if (p->timer_b == 0) {
+ p->timer_b = global_timer_b; /* Set timer B if not set (RFC 3261) */
+ }
+ ms = p->timer_t1 * 64;
}
-
- register_peer_exten(peer, FALSE);
- ast_free_ha(peer->ha);
- if (peer->selfdestruct)
- ast_atomic_fetchadd_int(&apeerobjs, -1);
- else if (peer->is_realtime) {
- ast_atomic_fetchadd_int(&rpeerobjs, -1);
- ast_debug(3, "-REALTIME- peer Destroyed. Name: %s. Realtime Peer objects: %d\n", peer->name, rpeerobjs);
- } else
- ast_atomic_fetchadd_int(&speerobjs, -1);
- clear_realm_authentication(peer->auth);
- peer->auth = NULL;
- if (peer->dnsmgr)
- ast_dnsmgr_release(peer->dnsmgr);
- clear_peer_mailboxes(peer);
+ if (sip_debug_test_pvt(p))
+ ast_verbose("Scheduling destruction of SIP dialog '%s' in %d ms (Method: %s)\n", p->callid, ms, sip_methods[p->method].text);
+ if (sip_cancel_destroy(p))
+ ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
- if (peer->socket.tcptls_session) {
- ao2_ref(peer->socket.tcptls_session, -1);
- peer->socket.tcptls_session = NULL;
- }
+ if (p->do_history)
+ append_history(p, "SchedDestroy", "%d ms", ms);
+ p->autokillid = ast_sched_add(sched, ms, __sip_autodestruct, dialog_ref(p, "setting ref as passing into ast_sched_add for __sip_autodestruct"));
- ast_string_field_free_memory(peer);
+ if (p->stimer && p->stimer->st_active == TRUE && p->stimer->st_schedid > 0)
+ stop_session_timer(p);
}
-/*! \brief Update peer data in database (if used) */
-static void update_peer(struct sip_peer *p, int expire)
+/*! \brief Cancel destruction of SIP dialog.
+ * Be careful as this also absorbs the reference - if you call it
+ * from within the scheduler, this might be the last reference.
+ */
+int sip_cancel_destroy(struct sip_pvt *p)
{
- int rtcachefriends = ast_test_flag(&p->flags[1], SIP_PAGE2_RTCACHEFRIENDS);
- if (sip_cfg.peer_rtupdate &&
- (p->is_realtime || rtcachefriends)) {
- realtime_update_peer(p->name, &p->addr, p->username, rtcachefriends ? p->fullcontact : NULL, p->useragent, expire, p->deprecated_username, p->lastms);
- }
-}
+ int res = 0;
+ if (p->autokillid > -1) {
+ int res3;
-static struct ast_variable *get_insecure_variable_from_config(struct ast_config *cfg)
-{
- struct ast_variable *var = NULL;
- struct ast_flags flags = {0};
- char *cat = NULL;
- const char *insecure;
- while ((cat = ast_category_browse(cfg, cat))) {
- insecure = ast_variable_retrieve(cfg, cat, "insecure");
- set_insecure_flags(&flags, insecure, -1);
- if (ast_test_flag(&flags, SIP_INSECURE_PORT)) {
- var = ast_category_root(cfg, cat);
- break;
+ if (!(res3 = ast_sched_del(sched, p->autokillid))) {
+ append_history(p, "CancelDestroy", "");
+ p->autokillid = -1;
+ dialog_unref(p, "dialog unrefd because autokillid is de-sched'd");
}
}
- return var;
+ return res;
}
-static const char *get_name_from_variable(struct ast_variable *var, const char *newpeername)
+/*! \brief Acknowledges receipt of a packet and stops retransmission
+ * called with p locked*/
+int __sip_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
{
- struct ast_variable *tmp;
- for (tmp = var; tmp; tmp = tmp->next) {
- if (!newpeername && !strcasecmp(tmp->name, "name"))
- newpeername = tmp->value;
+ struct sip_pkt *cur, *prev = NULL;
+ const char *msg = "Not Found"; /* used only for debugging */
+ int res = FALSE;
+
+ /* If we have an outbound proxy for this dialog, then delete it now since
+ the rest of the requests in this dialog needs to follow the routing.
+ If obforcing is set, we will keep the outbound proxy during the whole
+ dialog, regardless of what the SIP rfc says
+ */
+ if (p->outboundproxy && !p->outboundproxy->force){
+ ref_proxy(p, NULL);
}
- return newpeername;
-}
-/*! \brief realtime_peer: Get peer from realtime storage
- * Checks the "sippeers" realtime family from extconfig.conf
- * Checks the "sipregs" realtime family from extconfig.conf if it's configured.
- * This returns a pointer to a peer and because we use build_peer, we can rest
- * assured that the refcount is bumped.
-*/
-static struct sip_peer *realtime_peer(const char *newpeername, struct sockaddr_in *sin, int devstate_only)
-{
- struct sip_peer *peer;
- struct ast_variable *var = NULL;
- struct ast_variable *varregs = NULL;
- struct ast_variable *tmp;
- struct ast_config *peerlist = NULL;
- char ipaddr[INET_ADDRSTRLEN];
- char portstring[6]; /*up to 5 digits plus null terminator*/
- char *cat = NULL;
- unsigned short portnum;
- int realtimeregs = ast_check_realtime("sipregs");
-
- /* First check on peer name */
- if (newpeername) {
- if (realtimeregs)
- varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
-
- var = ast_load_realtime("sippeers", "name", newpeername, "host", "dynamic", SENTINEL);
- if (!var && sin)
- var = ast_load_realtime("sippeers", "name", newpeername, "host", ast_inet_ntoa(sin->sin_addr), SENTINEL);
- if (!var) {
- var = ast_load_realtime("sippeers", "name", newpeername, SENTINEL);
- /*!\note
- * If this one loaded something, then we need to ensure that the host
- * field matched. The only reason why we can't have this as a criteria
- * is because we only have the IP address and the host field might be
- * set as a name (and the reverse PTR might not match).
+ for (cur = p->packets; cur; prev = cur, cur = cur->next) {
+ if (cur->seqno != seqno || cur->is_resp != resp)
+ continue;
+ if (cur->is_resp || cur->method == sipmethod) {
+ res = TRUE;
+ msg = "Found";
+ if (!resp && (seqno == p->pendinginvite)) {
+ ast_debug(1, "Acked pending invite %d\n", p->pendinginvite);
+ p->pendinginvite = 0;
+ }
+ if (cur->retransid > -1) {
+ if (sipdebug)
+ ast_debug(4, "** SIP TIMER: Cancelling retransmit of packet (reply received) Retransid #%d\n", cur->retransid);
+ }
+ /* This odd section is designed to thwart a
+ * race condition in the packet scheduler. There are
+ * two conditions under which deleting the packet from the
+ * scheduler can fail.
+ *
+ * 1. The packet has been removed from the scheduler because retransmission
+ * is being attempted. The problem is that if the packet is currently attempting
+ * retransmission and we are at this point in the code, then that MUST mean
+ * that retrans_pkt is waiting on p's lock. Therefore we will relinquish the
+ * lock temporarily to allow retransmission.
+ *
+ * 2. The packet has reached its maximum number of retransmissions and has
+ * been permanently removed from the packet scheduler. If this is the case, then
+ * the packet's retransid will be set to -1. The atomicity of the setting and checking
+ * of the retransid to -1 is ensured since in both cases p's lock is held.
*/
- if (var && sin) {
- for (tmp = var; tmp; tmp = tmp->next) {
- if (!strcasecmp(tmp->name, "host")) {
- struct hostent *hp;
- struct ast_hostent ahp;
- if (!(hp = ast_gethostbyname(tmp->value, &ahp)) || (memcmp(hp->h_addr, &sin->sin_addr, sizeof(hp->h_addr)))) {
- /* No match */
- ast_variables_destroy(var);
- var = NULL;
- }
- break;
- }
- }
+ while (cur->retransid > -1 && ast_sched_del(sched, cur->retransid)) {
+ sip_pvt_unlock(p);
+ usleep(1);
+ sip_pvt_lock(p);
}
+ UNLINK(cur, p->packets, prev);
+ dialog_unref(cur->owner, "unref pkt cur->owner dialog from sip ack before freeing pkt");
+ if (cur->data)
+ ast_free(cur->data);
+ ast_free(cur);
+ break;
}
}
+ ast_debug(1, "Stopping retransmission on '%s' of %s %d: Match %s\n",
+ p->callid, resp ? "Response" : "Request", seqno, msg);
+ return res;
+}
- if (!var && sin) { /* Then check on IP address for dynamic peers */
- ast_copy_string(ipaddr, ast_inet_ntoa(sin->sin_addr), sizeof(ipaddr));
- portnum = ntohs(sin->sin_port);
- sprintf(portstring, "%u", portnum);
- var = ast_load_realtime("sippeers", "host", ipaddr, "port", portstring, SENTINEL); /* First check for fixed IP hosts */
- if (var) {
- if (realtimeregs) {
- newpeername = get_name_from_variable(var, newpeername);
- varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
- }
- } else {
- if (realtimeregs)
- varregs = ast_load_realtime("sipregs", "ipaddr", ipaddr, "port", portstring, SENTINEL); /* Then check for registered hosts */
- else
- var = ast_load_realtime("sippeers", "ipaddr", ipaddr, "port", portstring, SENTINEL); /* Then check for registered hosts */
- if (varregs) {
- newpeername = get_name_from_variable(varregs, newpeername);
- var = ast_load_realtime("sippeers", "name", newpeername, SENTINEL);
- }
- }
- if (!var) { /*We couldn't match on ipaddress and port, so we need to check if port is insecure*/
- peerlist = ast_load_realtime_multientry("sippeers", "host", ipaddr, SENTINEL);
- if (peerlist) {
- var = get_insecure_variable_from_config(peerlist);
- if(var) {
- if (realtimeregs) {
- newpeername = get_name_from_variable(var, newpeername);
- varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
- }
- } else { /*var wasn't found in the list of "hosts", so try "ipaddr"*/
- peerlist = NULL;
- cat = NULL;
- peerlist = ast_load_realtime_multientry("sippeers", "ipaddr", ipaddr, SENTINEL);
- if(peerlist) {
- var = get_insecure_variable_from_config(peerlist);
- if(var) {
- if (realtimeregs) {
- newpeername = get_name_from_variable(var, newpeername);
- varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
- }
- }
- }
- }
- } else {
- if (realtimeregs) {
- peerlist = ast_load_realtime_multientry("sipregs", "ipaddr", ipaddr, SENTINEL);
- if (peerlist) {
- varregs = get_insecure_variable_from_config(peerlist);
- if (varregs) {
- newpeername = get_name_from_variable(varregs, newpeername);
- var = ast_load_realtime("sippeers", "name", newpeername, SENTINEL);
- }
- }
- } else {
- peerlist = ast_load_realtime_multientry("sippeers", "ipaddr", ipaddr, SENTINEL);
- if (peerlist) {
- var = get_insecure_variable_from_config(peerlist);
- if (var) {
- newpeername = get_name_from_variable(var, newpeername);
- varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
- }
- }
- }
- }
+/*! \brief Pretend to ack all packets
+ * called with p locked */
+void __sip_pretend_ack(struct sip_pvt *p)
+{
+ struct sip_pkt *cur = NULL;
+
+ while (p->packets) {
+ int method;
+ if (cur == p->packets) {
+ ast_log(LOG_WARNING, "Have a packet that doesn't want to give up! %s\n", sip_methods[cur->method].text);
+ return;
}
+ cur = p->packets;
+ method = (cur->method) ? cur->method : find_sip_method(cur->data->str);
+ __sip_ack(p, cur->seqno, cur->is_resp, method);
}
+}
- if (!var) {
- if (peerlist)
- ast_config_destroy(peerlist);
- return NULL;
- }
+/*! \brief Acks receipt of packet, keep it around (used for provisional responses) */
+int __sip_semi_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
+{
+ struct sip_pkt *cur;
+ int res = FALSE;
- for (tmp = var; tmp; tmp = tmp->next) {
- /* If this is type=user, then skip this object. */
- if (!strcasecmp(tmp->name, "type") &&
- !strcasecmp(tmp->value, "user")) {
- if(peerlist)
- ast_config_destroy(peerlist);
- else {
- ast_variables_destroy(var);
- ast_variables_destroy(varregs);
+ for (cur = p->packets; cur; cur = cur->next) {
+ if (cur->seqno == seqno && cur->is_resp == resp &&
+ (cur->is_resp || method_match(sipmethod, cur->data->str))) {
+ /* this is our baby */
+ if (cur->retransid > -1) {
+ if (sipdebug)
+ ast_debug(4, "*** SIP TIMER: Cancelling retransmission #%d - %s (got response)\n", cur->retransid, sip_methods[sipmethod].text);
}
- return NULL;
- } else if (!newpeername && !strcasecmp(tmp->name, "name")) {
- newpeername = tmp->value;
+ AST_SCHED_DEL(sched, cur->retransid);
+ res = TRUE;
+ break;
}
}
-
- if (!newpeername) { /* Did not find peer in realtime */
- ast_log(LOG_WARNING, "Cannot Determine peer name ip=%s\n", ipaddr);
- if(peerlist)
- ast_config_destroy(peerlist);
- else
- ast_variables_destroy(var);
- return NULL;
+ ast_debug(1, "(Provisional) Stopping retransmission (but retaining packet) on '%s' %s %d: %s\n", p->callid, resp ? "Response" : "Request", seqno, res == -1 ? "Not Found" : "Found");
+ return res;
+}
+
+
+/*! \brief Copy SIP request, parse it */
+static void parse_copy(struct sip_request *dst, const struct sip_request *src)
+{
+ copy_request(dst, src);
+ parse_request(dst);
+}
+
+/*! \brief add a blank line if no body */
+static void add_blank(struct sip_request *req)
+{
+ if (!req->lines) {
+ /* Add extra empty return. add_header() reserves 4 bytes so cannot be truncated */
+ ast_str_append(&req->data, 0, "\r\n");
+ req->len = ast_str_strlen(req->data);
}
+}
+
+static int send_provisional_keepalive_full(struct sip_pvt *pvt, int with_sdp)
+{
+ const char *msg = NULL;
+ if (!pvt->last_provisional || !strncasecmp(pvt->last_provisional, "100", 3)) {
+ msg = "183 Session Progress";
+ }
- /* Peer found in realtime, now build it in memory */
- peer = build_peer(newpeername, var, varregs, TRUE, devstate_only);
- if (!peer) {
- if(peerlist)
- ast_config_destroy(peerlist);
- else {
- ast_variables_destroy(var);
- ast_variables_destroy(varregs);
+ if (pvt->invitestate < INV_COMPLETED) {
+ if (with_sdp) {
+ transmit_response_with_sdp(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq, XMIT_UNRELIABLE, FALSE, FALSE);
+ } else {
+ transmit_response(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq);
}
- return NULL;
+ return PROVIS_KEEPALIVE_TIMEOUT;
}
- ast_debug(3, "-REALTIME- loading peer from database to memory. Name: %s. Peer objects: %d\n", peer->name, rpeerobjs);
+ return 0;
+}
- if (ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS) && !devstate_only) {
- /* Cache peer */
- ast_copy_flags(&peer->flags[1], &global_flags[1], SIP_PAGE2_RTAUTOCLEAR|SIP_PAGE2_RTCACHEFRIENDS);
- if (ast_test_flag(&global_flags[1], SIP_PAGE2_RTAUTOCLEAR)) {
- AST_SCHED_REPLACE_UNREF(peer->expire, sched, sip_cfg.rtautoclear * 1000, expire_register, peer,
- unref_peer(_data, "remove registration ref"),
- unref_peer(peer, "remove registration ref"),
- ref_peer(peer, "add registration ref"));
- }
- ao2_t_link(peers, peer, "link peer into peers table");
- if (peer->addr.sin_addr.s_addr) {
- ao2_t_link(peers_by_ip, peer, "link peer into peers_by_ip table");
- }
- }
- peer->is_realtime = 1;
- if (peerlist)
- ast_config_destroy(peerlist);
- else {
- ast_variables_destroy(var);
- ast_variables_destroy(varregs);
- }
+static int send_provisional_keepalive(const void *data) {
+ struct sip_pvt *pvt = (struct sip_pvt *) data;
- return peer;
+ return send_provisional_keepalive_full(pvt, 0);
}
-/* Function to assist finding peers by name only */
-static int find_by_name(void *obj, void *arg, void *data, int flags)
-{
- struct sip_peer *search = obj, *match = arg;
- int *which_objects = data;
+static int send_provisional_keepalive_with_sdp(const void *data) {
+ struct sip_pvt *pvt = (void *)data;
- /* Usernames in SIP uri's are case sensitive. Domains are not */
- if (strcmp(search->name, match->name)) {
- return 0;
- }
+ return send_provisional_keepalive_full(pvt, 1);
+}
- switch (*which_objects) {
- case FINDUSERS:
- if (!(search->type & SIP_TYPE_USER)) {
- return 0;
- }
- break;
- case FINDPEERS:
- if (!(search->type & SIP_TYPE_PEER)) {
- return 0;
- }
- break;
- case FINDALLDEVICES:
- break;
- }
+static void update_provisional_keepalive(struct sip_pvt *pvt, int with_sdp)
+{
+ AST_SCHED_DEL_UNREF(sched, pvt->provisional_keepalive_sched_id, dialog_unref(pvt, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
- return CMP_MATCH | CMP_STOP;
+ pvt->provisional_keepalive_sched_id = ast_sched_add(sched, PROVIS_KEEPALIVE_TIMEOUT,
+ with_sdp ? send_provisional_keepalive_with_sdp : send_provisional_keepalive, dialog_ref(pvt, "Increment refcount to pass dialog pointer to sched callback"));
}
-/*!
- * \brief Locate device by name or ip address
- *
- * \param which_objects Define which objects should be matched when doing a lookup
- * by name. Valid options are FINDUSERS, FINDPEERS, or FINDALLDEVICES.
- * Note that this option is not used at all when doing a lookup by IP.
- *
- * This is used on find matching device on name or ip/port.
- * If the device was declared as type=peer, we don't match on peer name on incoming INVITEs.
- *
- * \note Avoid using this function in new functions if there is a way to avoid it,
- * since it might cause a database lookup.
- */
-static struct sip_peer *find_peer(const char *peer, struct sockaddr_in *sin, int realtime, int which_objects, int devstate_only, int transport)
+/*! \brief Transmit response on SIP request*/
+static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno)
{
- struct sip_peer *p = NULL;
- struct sip_peer tmp_peer;
+ int res;
- if (peer) {
- ast_copy_string(tmp_peer.name, peer, sizeof(tmp_peer.name));
- p = ao2_t_callback_data(peers, OBJ_POINTER, find_by_name, &tmp_peer, &which_objects, "ao2_find in peers table");
- } else if (sin) { /* search by addr? */
- tmp_peer.addr.sin_addr.s_addr = sin->sin_addr.s_addr;
- tmp_peer.addr.sin_port = sin->sin_port;
- tmp_peer.flags[0].flags = 0;
- tmp_peer.transports = transport;
- p = ao2_t_find(peers_by_ip, &tmp_peer, OBJ_POINTER, "ao2_find in peers_by_ip table"); /* WAS: p = ASTOBJ_CONTAINER_FIND_FULL(&peerl, sin, name, sip_addr_hashfunc, 1, sip_addrcmp); */
- if (!p) {
- ast_set_flag(&tmp_peer.flags[0], SIP_INSECURE_PORT);
- p = ao2_t_find(peers_by_ip, &tmp_peer, OBJ_POINTER, "ao2_find in peers_by_ip table 2"); /* WAS: p = ASTOBJ_CONTAINER_FIND_FULL(&peerl, sin, name, sip_addr_hashfunc, 1, sip_addrcmp); */
- if (p) {
- return p;
- }
- }
+ add_blank(req);
+ if (sip_debug_test_pvt(p)) {
+ const struct sockaddr_in *dst = sip_real_dst(p);
+
+ ast_verbose("\n<--- %sTransmitting (%s) to %s:%d --->\n%s\n<------------>\n",
+ reliable ? "Reliably " : "", sip_nat_mode(p),
+ ast_inet_ntoa(dst->sin_addr),
+ ntohs(dst->sin_port), req->data->str);
+ }
+ if (p->do_history) {
+ struct sip_request tmp = { .rlPart1 = 0, };
+ parse_copy(&tmp, req);
+ append_history(p, reliable ? "TxRespRel" : "TxResp", "%s / %s - %s", tmp.data->str, get_header(&tmp, "CSeq"),
+ (tmp.method == SIP_RESPONSE || tmp.method == SIP_UNKNOWN) ? REQ_OFFSET_TO_STR(&tmp, rlPart2) : sip_methods[tmp.method].text);
+ ast_free(tmp.data);
}
- if (!p && (realtime || devstate_only)) {
- p = realtime_peer(peer, sin, devstate_only);
+ /* If we are sending a final response to an INVITE, stop retransmitting provisional responses */
+ if (p->initreq.method == SIP_INVITE && reliable == XMIT_CRITICAL) {
+ AST_SCHED_DEL_UNREF(sched, p->provisional_keepalive_sched_id, dialog_unref(p, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
}
- return p;
+ res = (reliable) ?
+ __sip_reliable_xmit(p, seqno, 1, req->data, req->len, (reliable == XMIT_CRITICAL), req->method) :
+ __sip_xmit(p, req->data, req->len);
+ ast_free(req->data);
+ req->data = NULL;
+ if (res > 0)
+ return 0;
+ return res;
}
-/*! \brief Set nat mode on the various data sockets */
-static void do_setnat(struct sip_pvt *p)
+/*! \brief Send SIP Request to the other part of the dialogue
+ \return see \ref __sip_xmit
+*/
+static int send_request(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno)
{
- const char *mode;
- int natflags;
-
- natflags = ast_test_flag(&p->flags[1], SIP_PAGE2_SYMMETRICRTP);
- mode = natflags ? "On" : "Off";
+ int res;
- if (p->rtp) {
- ast_debug(1, "Setting NAT on RTP to %s\n", mode);
- ast_rtp_instance_set_prop(p->rtp, AST_RTP_PROPERTY_NAT, natflags);
+ /* If we have an outbound proxy, reset peer address
+ Only do this once.
+ */
+ if (p->outboundproxy) {
+ p->sa = p->outboundproxy->ip;
}
- if (p->vrtp) {
- ast_debug(1, "Setting NAT on VRTP to %s\n", mode);
- ast_rtp_instance_set_prop(p->vrtp, AST_RTP_PROPERTY_NAT, natflags);
+
+ add_blank(req);
+ if (sip_debug_test_pvt(p)) {
+ if (ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT))
+ ast_verbose("%sTransmitting (NAT) to %s:%d:\n%s\n---\n", reliable ? "Reliably " : "", ast_inet_ntoa(p->recv.sin_addr), ntohs(p->recv.sin_port), req->data->str);
+ else
+ ast_verbose("%sTransmitting (no NAT) to %s:%d:\n%s\n---\n", reliable ? "Reliably " : "", ast_inet_ntoa(p->sa.sin_addr), ntohs(p->sa.sin_port), req->data->str);
}
- if (p->udptl) {
- ast_debug(1, "Setting NAT on UDPTL to %s\n", mode);
- ast_udptl_setnat(p->udptl, natflags);
+ if (p->do_history) {
+ struct sip_request tmp = { .rlPart1 = 0, };
+ parse_copy(&tmp, req);
+ append_history(p, reliable ? "TxReqRel" : "TxReq", "%s / %s - %s", tmp.data->str, get_header(&tmp, "CSeq"), sip_methods[tmp.method].text);
+ ast_free(tmp.data);
}
- if (p->trtp) {
- ast_debug(1, "Setting NAT on TRTP to %s\n", mode);
- ast_rtp_instance_set_prop(p->trtp, AST_RTP_PROPERTY_NAT, natflags);
+ res = (reliable) ?
+ __sip_reliable_xmit(p, seqno, 0, req->data, req->len, (reliable == XMIT_CRITICAL), req->method) :
+ __sip_xmit(p, req->data, req->len);
+ if (req->data) {
+ ast_free(req->data);
+ req->data = NULL;
}
+ return res;
}
-/*! \brief Change the T38 state on a SIP dialog */
-static void change_t38_state(struct sip_pvt *p, int state)
+static void enable_dsp_detect(struct sip_pvt *p)
{
- int old = p->t38.state;
- struct ast_channel *chan = p->owner;
- struct ast_control_t38_parameters parameters = { .request_response = 0 };
-
- /* Don't bother changing if we are already in the state wanted */
- if (old == state)
- return;
-
- p->t38.state = state;
- ast_debug(2, "T38 state changed to %d on channel %s\n", p->t38.state, chan ? chan->name : "<none>");
+ int features = 0;
- /* If no channel was provided we can't send off a control frame */
- if (!chan)
+ if (p->dsp) {
return;
-
- /* Given the state requested and old state determine what control frame we want to queue up */
- switch (state) {
- case T38_PEER_REINVITE:
- parameters = p->t38.their_parms;
- parameters.max_ifp = ast_udptl_get_far_max_ifp(p->udptl);
- parameters.request_response = AST_T38_REQUEST_NEGOTIATE;
- ast_udptl_set_tag(p->udptl, "SIP/%s", p->username);
- break;
- case T38_ENABLED:
- parameters = p->t38.their_parms;
- parameters.max_ifp = ast_udptl_get_far_max_ifp(p->udptl);
- parameters.request_response = AST_T38_NEGOTIATED;
- ast_udptl_set_tag(p->udptl, "SIP/%s", p->username);
- break;
- case T38_DISABLED:
- if (old == T38_ENABLED) {
- parameters.request_response = AST_T38_TERMINATED;
- } else if (old == T38_LOCAL_REINVITE) {
- parameters.request_response = AST_T38_REFUSED;
- }
- break;
- case T38_LOCAL_REINVITE:
- /* wait until we get a peer response before responding to local reinvite */
- break;
}
- /* Woot we got a message, create a control frame and send it on! */
- if (parameters.request_response)
- ast_queue_control_data(chan, AST_CONTROL_T38_PARAMETERS, ¶meters, sizeof(parameters));
-}
+ if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) ||
+ (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) {
+ if (!p->rtp || ast_rtp_instance_dtmf_mode_set(p->rtp, AST_RTP_DTMF_MODE_INBAND)) {
+ features |= DSP_FEATURE_DIGIT_DETECT;
+ }
+ }
-/*! \brief Set the global T38 capabilities on a SIP dialog structure */
-static void set_t38_capabilities(struct sip_pvt *p)
-{
- if (p->udptl) {
- if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) == SIP_PAGE2_T38SUPPORT_UDPTL_REDUNDANCY) {
- ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_REDUNDANCY);
- } else if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) == SIP_PAGE2_T38SUPPORT_UDPTL_FEC) {
- ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_FEC);
- } else if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) == SIP_PAGE2_T38SUPPORT_UDPTL) {
- ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_NONE);
- }
+ if (ast_test_flag(&p->flags[1], SIP_PAGE2_FAX_DETECT_CNG)) {
+ features |= DSP_FEATURE_FAX_DETECT;
}
-}
-static void copy_socket_data(struct sip_socket *to_sock, const struct sip_socket *from_sock)
-{
- if (to_sock->tcptls_session) {
- ao2_ref(to_sock->tcptls_session, -1);
- to_sock->tcptls_session = NULL;
+ if (!features) {
+ return;
}
- if (from_sock->tcptls_session) {
- ao2_ref(from_sock->tcptls_session, +1);
+ if (!(p->dsp = ast_dsp_new())) {
+ return;
}
- *to_sock = *from_sock;
+ ast_dsp_set_features(p->dsp, features);
+ if (global_relaxdtmf) {
+ ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF);
+ }
}
-/*! \brief Initialize RTP portion of a dialog
- * \return -1 on failure, 0 on success
- */
-static int dialog_initialize_rtp(struct sip_pvt *dialog)
+static void disable_dsp_detect(struct sip_pvt *p)
{
- if (!sip_methods[dialog->method].need_rtp) {
- return 0;
+ if (p->dsp) {
+ ast_dsp_free(p->dsp);
+ p->dsp = NULL;
}
+}
- if (!(dialog->rtp = ast_rtp_instance_new(dialog->engine, sched, &bindaddr, NULL))) {
- return -1;
- }
+/*! \brief Set an option on a SIP dialog */
+static int sip_setoption(struct ast_channel *chan, int option, void *data, int datalen)
+{
+ int res = -1;
+ struct sip_pvt *p = chan->tech_pvt;
- if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_VIDEOSUPPORT) && (dialog->capability & AST_FORMAT_VIDEO_MASK)) {
- if (!(dialog->vrtp = ast_rtp_instance_new(dialog->engine, sched, &bindaddr, NULL))) {
- return -1;
- }
- ast_rtp_instance_set_timeout(dialog->vrtp, global_rtptimeout);
- ast_rtp_instance_set_hold_timeout(dialog->vrtp, global_rtpholdtimeout);
+ switch (option) {
+ case AST_OPTION_FORMAT_READ:
+ res = ast_rtp_instance_set_read_format(p->rtp, *(int *) data);
+ break;
+ case AST_OPTION_FORMAT_WRITE:
+ res = ast_rtp_instance_set_write_format(p->rtp, *(int *) data);
+ break;
+ case AST_OPTION_MAKE_COMPATIBLE:
+ res = ast_rtp_instance_make_compatible(chan, p->rtp, (struct ast_channel *) data);
+ break;
+ case AST_OPTION_DIGIT_DETECT:
+ if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) ||
+ (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) {
+ char *cp = (char *) data;
- ast_rtp_instance_set_prop(dialog->vrtp, AST_RTP_PROPERTY_RTCP, 1);
+ ast_debug(1, "%sabling digit detection on %s\n", *cp ? "En" : "Dis", chan->name);
+ if (*cp) {
+ enable_dsp_detect(p);
+ } else {
+ disable_dsp_detect(p);
+ }
+ res = 0;
+ }
+ break;
+ default:
+ break;
}
- if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_TEXTSUPPORT)) {
- if (!(dialog->trtp = ast_rtp_instance_new(dialog->engine, sched, &bindaddr, NULL))) {
+ return res;
+}
+
+/*! \brief Query an option on a SIP dialog */
+static int sip_queryoption(struct ast_channel *chan, int option, void *data, int *datalen)
+{
+ int res = -1;
+ enum ast_t38_state state = T38_STATE_UNAVAILABLE;
+ struct sip_pvt *p = (struct sip_pvt *) chan->tech_pvt;
+ char *cp;
+
+ switch (option) {
+ case AST_OPTION_T38_STATE:
+ /* Make sure we got an ast_t38_state enum passed in */
+ if (*datalen != sizeof(enum ast_t38_state)) {
+ ast_log(LOG_ERROR, "Invalid datalen for AST_OPTION_T38_STATE option. Expected %d, got %d\n", (int)sizeof(enum ast_t38_state), *datalen);
return -1;
}
- ast_rtp_instance_set_timeout(dialog->trtp, global_rtptimeout);
- ast_rtp_instance_set_hold_timeout(dialog->trtp, global_rtpholdtimeout);
- ast_rtp_instance_set_prop(dialog->trtp, AST_RTP_PROPERTY_RTCP, 1);
- }
+ sip_pvt_lock(p);
- ast_rtp_instance_set_timeout(dialog->rtp, global_rtptimeout);
- ast_rtp_instance_set_hold_timeout(dialog->rtp, global_rtpholdtimeout);
+ /* Now if T38 support is enabled we need to look and see what the current state is to get what we want to report back */
+ if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT)) {
+ switch (p->t38.state) {
+ case T38_LOCAL_REINVITE:
+ case T38_PEER_REINVITE:
+ state = T38_STATE_NEGOTIATING;
+ break;
+ case T38_ENABLED:
+ state = T38_STATE_NEGOTIATED;
+ break;
+ default:
+ state = T38_STATE_UNKNOWN;
+ }
+ }
- ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_RTCP, 1);
- ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF, ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833);
- ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, ast_test_flag(&dialog->flags[1], SIP_PAGE2_RFC2833_COMPENSATE));
+ sip_pvt_unlock(p);
- ast_rtp_instance_set_qos(dialog->rtp, global_tos_audio, 0, "SIP RTP");
+ *((enum ast_t38_state *) data) = state;
+ res = 0;
- do_setnat(dialog);
+ break;
+ case AST_OPTION_DIGIT_DETECT:
+ cp = (char *) data;
+ *cp = p->dsp ? 1 : 0;
+ ast_debug(1, "Reporting digit detection %sabled on %s\n", *cp ? "en" : "dis", chan->name);
+ break;
+ case AST_OPTION_DEVICE_NAME:
+ if (p && p->outgoing_call) {
+ cp = (char *) data;
+ ast_copy_string(cp, p->dialstring, *datalen);
+ res = 0;
+ }
+ /* We purposely break with a return of -1 in the
+ * implied else case here
+ */
+ break;
+ default:
+ break;
+ }
- return 0;
+ return res;
}
-/*! \brief Create address structure from peer reference.
- * This function copies data from peer to the dialog, so we don't have to look up the peer
- * again from memory or database during the life time of the dialog.
- *
- * \return -1 on error, 0 on success.
- *
+/*! \brief Locate closing quote in a string, skipping escaped quotes.
+ * optionally with a limit on the search.
+ * start must be past the first quote.
*/
-static int create_addr_from_peer(struct sip_pvt *dialog, struct sip_peer *peer)
+const char *find_closing_quote(const char *start, const char *lim)
{
+ char last_char = '\0';
+ const char *s;
+ for (s = start; *s && s != lim; last_char = *s++) {
+ if (*s == '"' && last_char != '\\')
+ break;
+ }
+ return s;
+}
- /* this checks that the dialog is contacting the peer on a valid
- * transport type based on the peers transport configuration,
- * otherwise, this function bails out */
- if (dialog->socket.type && check_request_transport(peer, dialog))
- return -1;
- copy_socket_data(&dialog->socket, &peer->socket);
+/*! \brief Send message with Access-URL header, if this is an HTML URL only! */
+static int sip_sendhtml(struct ast_channel *chan, int subclass, const char *data, int datalen)
+{
+ struct sip_pvt *p = chan->tech_pvt;
- if ((peer->addr.sin_addr.s_addr || peer->defaddr.sin_addr.s_addr) &&
- (!peer->maxms || ((peer->lastms >= 0) && (peer->lastms <= peer->maxms)))) {
- dialog->sa = (peer->addr.sin_addr.s_addr) ? peer->addr : peer->defaddr;
- dialog->recv = dialog->sa;
- } else
+ if (subclass != AST_HTML_URL)
return -1;
- ast_copy_flags(&dialog->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY);
- ast_copy_flags(&dialog->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY);
- dialog->capability = peer->capability;
- dialog->prefs = peer->prefs;
- if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_T38SUPPORT)) {
- if (!dialog->udptl) {
- /* t38pt_udptl was enabled in the peer and not in [general] */
- dialog->udptl = ast_udptl_new_with_bindaddr(sched, io, 0, bindaddr.sin_addr);
- }
- dialog->t38_maxdatagram = peer->t38_maxdatagram;
- set_t38_capabilities(dialog);
- } else if (dialog->udptl) {
- ast_udptl_destroy(dialog->udptl);
- dialog->udptl = NULL;
+ ast_string_field_build(p, url, "<%s>;mode=active", data);
+
+ if (sip_debug_test_pvt(p))
+ ast_debug(1, "Send URL %s, state = %d!\n", data, chan->_state);
+
+ switch (chan->_state) {
+ case AST_STATE_RING:
+ transmit_response(p, "100 Trying", &p->initreq);
+ break;
+ case AST_STATE_RINGING:
+ transmit_response(p, "180 Ringing", &p->initreq);
+ break;
+ case AST_STATE_UP:
+ if (!p->pendinginvite) { /* We are up, and have no outstanding invite */
+ transmit_reinvite_with_sdp(p, FALSE, FALSE);
+ } else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
+ ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
+ }
+ break;
+ default:
+ ast_log(LOG_WARNING, "Don't know how to send URI when state is %d!\n", chan->_state);
}
- ast_string_field_set(dialog, engine, peer->engine);
+ return 0;
+}
- if (dialog_initialize_rtp(dialog)) {
+/*! \brief Deliver SIP call ID for the call */
+static const char *sip_get_callid(struct ast_channel *chan)
+{
+ return chan->tech_pvt ? ((struct sip_pvt *) chan->tech_pvt)->callid : "";
+}
+
+/*! \brief Send SIP MESSAGE text within a call
+ Called from PBX core sendtext() application */
+static int sip_sendtext(struct ast_channel *ast, const char *text)
+{
+ struct sip_pvt *dialog = ast->tech_pvt;
+ int debug = sip_debug_test_pvt(dialog);
+
+ if (!dialog)
return -1;
+ /* NOT ast_strlen_zero, because a zero-length message is specifically
+ * allowed by RFC 3428 (See section 10, Examples) */
+ if (!text)
+ return 0;
+ if(!is_method_allowed(&dialog->allowed_methods, SIP_MESSAGE)) {
+ ast_debug(2, "Trying to send MESSAGE to device that does not support it.\n");
+ return(0);
}
+ if (debug)
+ ast_verbose("Sending text %s on %s\n", text, ast->name);
+ transmit_message_with_text(dialog, text);
+ return 0;
+}
- if (dialog->rtp) { /* Audio */
- ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF, ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833);
- ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, ast_test_flag(&dialog->flags[1], SIP_PAGE2_RFC2833_COMPENSATE));
- ast_rtp_instance_set_timeout(dialog->rtp, peer->rtptimeout);
- ast_rtp_instance_set_hold_timeout(dialog->rtp, peer->rtpholdtimeout);
- /* Set Frame packetization */
- ast_rtp_codecs_packetization_set(ast_rtp_instance_get_codecs(dialog->rtp), dialog->rtp, &dialog->prefs);
- dialog->autoframing = peer->autoframing;
- }
- if (dialog->vrtp) { /* Video */
- ast_rtp_instance_set_timeout(dialog->vrtp, peer->rtptimeout);
- ast_rtp_instance_set_hold_timeout(dialog->vrtp, peer->rtpholdtimeout);
- }
- if (dialog->trtp) { /* Realtime text */
- ast_rtp_instance_set_timeout(dialog->trtp, peer->rtptimeout);
- ast_rtp_instance_set_hold_timeout(dialog->trtp, peer->rtpholdtimeout);
- }
+/*! \brief Update peer object in realtime storage
+ If the Asterisk system name is set in asterisk.conf, we will use
+ that name and store that in the "regserver" field in the sippeers
+ table to facilitate multi-server setups.
+*/
+static void realtime_update_peer(const char *peername, struct sockaddr_in *sin, const char *defaultuser, const char *fullcontact, const char *useragent, int expirey, unsigned short deprecated_username, int lastms)
+{
+ char port[10];
+ char ipaddr[INET_ADDRSTRLEN];
+ char regseconds[20];
+ char *tablename = NULL;
+ char str_lastms[20];
- ast_string_field_set(dialog, peername, peer->name);
- ast_string_field_set(dialog, authname, peer->username);
- ast_string_field_set(dialog, username, peer->username);
- ast_string_field_set(dialog, peersecret, peer->secret);
- ast_string_field_set(dialog, peermd5secret, peer->md5secret);
- ast_string_field_set(dialog, mohsuggest, peer->mohsuggest);
- ast_string_field_set(dialog, mohinterpret, peer->mohinterpret);
- ast_string_field_set(dialog, tohost, peer->tohost);
- ast_string_field_set(dialog, fullcontact, peer->fullcontact);
- ast_string_field_set(dialog, accountcode, peer->accountcode);
- ast_string_field_set(dialog, context, peer->context);
- ast_string_field_set(dialog, cid_num, peer->cid_num);
- ast_string_field_set(dialog, cid_name, peer->cid_name);
- ast_string_field_set(dialog, mwi_from, peer->mwi_from);
- ast_string_field_set(dialog, parkinglot, peer->parkinglot);
- ast_string_field_set(dialog, engine, peer->engine);
- ref_proxy(dialog, obproxy_get(dialog, peer));
- dialog->callgroup = peer->callgroup;
- dialog->pickupgroup = peer->pickupgroup;
- dialog->allowtransfer = peer->allowtransfer;
- dialog->jointnoncodeccapability = dialog->noncodeccapability;
- dialog->rtptimeout = peer->rtptimeout;
- dialog->peerauth = peer->auth;
- dialog->maxcallbitrate = peer->maxcallbitrate;
- dialog->disallowed_methods = peer->disallowed_methods;
- if (ast_strlen_zero(dialog->tohost))
- ast_string_field_set(dialog, tohost, ast_inet_ntoa(dialog->sa.sin_addr));
- if (!ast_strlen_zero(peer->fromdomain)) {
- ast_string_field_set(dialog, fromdomain, peer->fromdomain);
- if (!dialog->initreq.headers) {
- char *c;
- char *tmpcall = ast_strdupa(dialog->callid);
- /* this sure looks to me like we are going to change the callid on this dialog!! */
- c = strchr(tmpcall, '@');
- if (c) {
- *c = '\0';
- ao2_t_unlink(dialogs, dialog, "About to change the callid -- remove the old name");
- ast_string_field_build(dialog, callid, "%s@%s", tmpcall, peer->fromdomain);
- ao2_t_link(dialogs, dialog, "New dialog callid -- inserted back into table");
- }
- }
- }
- if (!ast_strlen_zero(peer->fromuser))
- ast_string_field_set(dialog, fromuser, peer->fromuser);
- if (!ast_strlen_zero(peer->language))
- ast_string_field_set(dialog, language, peer->language);
- /* Set timer T1 to RTT for this peer (if known by qualify=) */
- /* Minimum is settable or default to 100 ms */
- /* If there is a maxms and lastms from a qualify use that over a manual T1
- value. Otherwise, use the peer's T1 value. */
- if (peer->maxms && peer->lastms)
- dialog->timer_t1 = peer->lastms < global_t1min ? global_t1min : peer->lastms;
- else
- dialog->timer_t1 = peer->timer_t1;
+ const char *sysname = ast_config_AST_SYSTEM_NAME;
+ char *syslabel = NULL;
- /* Set timer B to control transaction timeouts, the peer setting is the default and overrides
- the known timer */
- if (peer->timer_b)
- dialog->timer_b = peer->timer_b;
- else
- dialog->timer_b = 64 * dialog->timer_t1;
+ time_t nowtime = time(NULL) + expirey;
+ const char *fc = fullcontact ? "fullcontact" : NULL;
- if ((ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833) ||
- (ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_AUTO))
- dialog->noncodeccapability |= AST_RTP_DTMF;
- else
- dialog->noncodeccapability &= ~AST_RTP_DTMF;
- if (peer->call_limit)
- ast_set_flag(&dialog->flags[0], SIP_CALL_LIMIT);
- if (!dialog->portinuri)
- dialog->portinuri = peer->portinuri;
+ int realtimeregs = ast_check_realtime("sipregs");
+
+ tablename = realtimeregs ? "sipregs" : "sippeers";
- dialog->chanvars = copy_vars(peer->chanvars);
- return 0;
+ snprintf(str_lastms, sizeof(str_lastms), "%d", lastms);
+ snprintf(regseconds, sizeof(regseconds), "%d", (int)nowtime); /* Expiration time */
+ ast_copy_string(ipaddr, ast_inet_ntoa(sin->sin_addr), sizeof(ipaddr));
+ snprintf(port, sizeof(port), "%d", ntohs(sin->sin_port));
+
+ if (ast_strlen_zero(sysname)) /* No system name, disable this */
+ sysname = NULL;
+ else if (sip_cfg.rtsave_sysname)
+ syslabel = "regserver";
+
+ if (fc) {
+ ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
+ "port", port, "regseconds", regseconds,
+ deprecated_username ? "username" : "defaultuser", defaultuser,
+ "useragent", useragent, "lastms", str_lastms,
+ fc, fullcontact, syslabel, sysname, SENTINEL); /* note fc and syslabel _can_ be NULL */
+ } else {
+ ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
+ "port", port, "regseconds", regseconds,
+ "useragent", useragent, "lastms", str_lastms,
+ deprecated_username ? "username" : "defaultuser", defaultuser,
+ syslabel, sysname, SENTINEL); /* note syslabel _can_ be NULL */
+ }
}
-/*! \brief create address structure from device name
- * Or, if peer not found, find it in the global DNS
- * returns TRUE (-1) on failure, FALSE on success */
-static int create_addr(struct sip_pvt *dialog, const char *opeer, struct sockaddr_in *sin, int newdialog, struct sockaddr_in *remote_address)
+/*! \brief Automatically add peer extension to dial plan */
+static void register_peer_exten(struct sip_peer *peer, int onoff)
{
- struct hostent *hp;
- struct ast_hostent ahp;
- struct sip_peer *peer;
- char *port;
- int portno = 0;
- char host[MAXHOSTNAMELEN], *hostn;
- char peername[256];
- int srv_ret = 0;
+ char multi[256];
+ char *stringp, *ext, *context;
+ struct pbx_find_info q = { .stacklen = 0 };
- ast_copy_string(peername, opeer, sizeof(peername));
- port = strchr(peername, ':');
- if (port) {
- *port++ = '\0';
- dialog->portinuri = 1;
- }
- dialog->sa.sin_family = AF_INET;
- dialog->timer_t1 = global_t1; /* Default SIP retransmission timer T1 (RFC 3261) */
- dialog->timer_b = global_timer_b; /* Default SIP transaction timer B (RFC 3261) */
- peer = find_peer(peername, NULL, TRUE, FINDPEERS, FALSE, 0);
+ /* XXX note that sip_cfg.regcontext is both a global 'enable' flag and
+ * the name of the global regexten context, if not specified
+ * individually.
+ */
+ if (ast_strlen_zero(sip_cfg.regcontext))
+ return;
- if (peer) {
- int res;
- if (newdialog) {
- set_socket_transport(&dialog->socket, 0);
- }
- res = create_addr_from_peer(dialog, peer);
- if (remote_address && remote_address->sin_addr.s_addr) {
- dialog->sa = dialog->recv = *remote_address;
- } else if (!ast_strlen_zero(port)) {
- if ((portno = atoi(port))) {
- dialog->sa.sin_port = dialog->recv.sin_port = htons(portno);
+ ast_copy_string(multi, S_OR(peer->regexten, peer->name), sizeof(multi));
+ stringp = multi;
+ while ((ext = strsep(&stringp, "&"))) {
+ if ((context = strchr(ext, '@'))) {
+ *context++ = '\0'; /* split ext@context */
+ if (!ast_context_find(context)) {
+ ast_log(LOG_WARNING, "Context %s must exist in regcontext= in sip.conf!\n", context);
+ continue;
}
- }
- unref_peer(peer, "create_addr: unref peer from find_peer hashtab lookup");
- return res;
- }
-
- if (dialog_initialize_rtp(dialog)) {
- return -1;
- }
-
- ast_string_field_set(dialog, tohost, peername);
- dialog->allowed_methods &= ~sip_cfg.disallowed_methods;
-
- /* Get the outbound proxy information */
- ref_proxy(dialog, obproxy_get(dialog, NULL));
-
- if (sin) {
- /* This address should be updated using dnsmgr */
- memcpy(&dialog->sa.sin_addr, &sin->sin_addr, sizeof(dialog->sa.sin_addr));
- if (!sin->sin_port) {
- portno = port_str2int(port, (dialog->socket.type == SIP_TRANSPORT_TLS) ? STANDARD_TLS_PORT : STANDARD_SIP_PORT);
} else {
- portno = ntohs(sin->sin_port);
+ context = sip_cfg.regcontext;
}
- } else {
-
- /* Let's see if we can find the host in DNS. First try DNS SRV records,
- then hostname lookup */
- /*! \todo Fix this function. When we ask for SRV, we should check all transports
- In the future, we should first check NAPTR to find out transport preference
- */
- hostn = peername;
- /* Section 4.2 of RFC 3263 specifies that if a port number is specified, then
- * an A record lookup should be used instead of SRV.
- */
- if (!port && sip_cfg.srvlookup) {
- char service[MAXHOSTNAMELEN];
- int tportno;
-
- snprintf(service, sizeof(service), "_sip._%s.%s", get_transport(dialog->socket.type), peername);
- srv_ret = ast_get_srv(NULL, host, sizeof(host), &tportno, service);
- if (srv_ret > 0) {
- hostn = host;
- portno = tportno;
+ if (onoff) {
+ if (!ast_exists_extension(NULL, context, ext, 1, NULL)) {
+ ast_add_extension(context, 1, ext, 1, NULL, NULL, "Noop",
+ ast_strdup(peer->name), ast_free_ptr, "SIP");
}
+ } else if (pbx_find_extension(NULL, NULL, &q, context, ext, 1, NULL, "", E_MATCH)) {
+ ast_context_remove_extension(context, ext, 1, NULL);
}
- if (!portno)
- portno = port_str2int(port, (dialog->socket.type == SIP_TRANSPORT_TLS) ? STANDARD_TLS_PORT : STANDARD_SIP_PORT);
- hp = ast_gethostbyname(hostn, &ahp);
- if (!hp) {
- ast_log(LOG_WARNING, "No such host: %s\n", peername);
- return -1;
- }
- memcpy(&dialog->sa.sin_addr, hp->h_addr, sizeof(dialog->sa.sin_addr));
}
-
- if (!dialog->socket.type)
- set_socket_transport(&dialog->socket, SIP_TRANSPORT_UDP);
- if (!dialog->socket.port)
- dialog->socket.port = bindaddr.sin_port;
- dialog->sa.sin_port = htons(portno);
- dialog->recv = dialog->sa;
- return 0;
}
-/*! \brief Scheduled congestion on a call.
- * Only called by the scheduler, must return the reference when done.
- */
-static int auto_congest(const void *arg)
+/*! Destroy mailbox subscriptions */
+static void destroy_mailbox(struct sip_mailbox *mailbox)
{
- struct sip_pvt *p = (struct sip_pvt *)arg;
+ if (mailbox->mailbox)
+ ast_free(mailbox->mailbox);
+ if (mailbox->context)
+ ast_free(mailbox->context);
+ if (mailbox->event_sub)
+ ast_event_unsubscribe(mailbox->event_sub);
+ ast_free(mailbox);
+}
- sip_pvt_lock(p);
- p->initid = -1; /* event gone, will not be rescheduled */
- if (p->owner) {
- /* XXX fails on possible deadlock */
- if (!ast_channel_trylock(p->owner)) {
- append_history(p, "Cong", "Auto-congesting (timer)");
- ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
- ast_channel_unlock(p->owner);
- }
+/*! Destroy all peer-related mailbox subscriptions */
+static void clear_peer_mailboxes(struct sip_peer *peer)
+{
+ struct sip_mailbox *mailbox;
- /* Give the channel a chance to act before we proceed with destruction */
- sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
- }
- sip_pvt_unlock(p);
- dialog_unref(p, "unreffing arg passed into auto_congest callback (p->initid)");
- return 0;
+ while ((mailbox = AST_LIST_REMOVE_HEAD(&peer->mailboxes, entry)))
+ destroy_mailbox(mailbox);
}
+static void sip_destroy_peer_fn(void *peer)
+{
+ sip_destroy_peer(peer);
+}
-/*! \brief Initiate SIP call from PBX
- * used from the dial() application */
-static int sip_call(struct ast_channel *ast, char *dest, int timeout)
+/*! \brief Destroy peer object from memory */
+static void sip_destroy_peer(struct sip_peer *peer)
{
- int res;
- struct sip_pvt *p = ast->tech_pvt; /* chan is locked, so the reference cannot go away */
- struct varshead *headp;
- struct ast_var_t *current;
- const char *referer = NULL; /* SIP referrer */
+ ast_debug(3, "Destroying SIP peer %s\n", peer->name);
+ if (peer->outboundproxy)
+ ao2_ref(peer->outboundproxy, -1);
+ peer->outboundproxy = NULL;
- if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
- ast_log(LOG_WARNING, "sip_call called on %s, neither down nor reserved\n", ast->name);
- return -1;
+ /* Delete it, it needs to disappear */
+ if (peer->call) {
+ dialog_unlink_all(peer->call, TRUE, TRUE);
+ peer->call = dialog_unref(peer->call, "peer->call is being unset");
}
+
- /* Check whether there is vxml_url, distinctive ring variables */
- headp=&ast->varshead;
- AST_LIST_TRAVERSE(headp, current, entries) {
- /* Check whether there is a VXML_URL variable */
- if (!p->options->vxml_url && !strcasecmp(ast_var_name(current), "VXML_URL")) {
- p->options->vxml_url = ast_var_value(current);
- } else if (!p->options->uri_options && !strcasecmp(ast_var_name(current), "SIP_URI_OPTIONS")) {
- p->options->uri_options = ast_var_value(current);
- } else if (!p->options->addsipheaders && !strncasecmp(ast_var_name(current), "SIPADDHEADER", strlen("SIPADDHEADER"))) {
- /* Check whether there is a variable with a name starting with SIPADDHEADER */
- p->options->addsipheaders = 1;
- } else if (!strcasecmp(ast_var_name(current), "SIPFROMDOMAIN")) {
- ast_string_field_set(p, fromdomain, ast_var_value(current));
- } else if (!strcasecmp(ast_var_name(current), "SIPTRANSFER")) {
- /* This is a transfered call */
- p->options->transfer = 1;
- } else if (!strcasecmp(ast_var_name(current), "SIPTRANSFER_REFERER")) {
- /* This is the referrer */
- referer = ast_var_value(current);
- } else if (!strcasecmp(ast_var_name(current), "SIPTRANSFER_REPLACES")) {
- /* We're replacing a call. */
- p->options->replaces = ast_var_value(current);
- }
+ if (peer->mwipvt) { /* We have an active subscription, delete it */
+ dialog_unlink_all(peer->mwipvt, TRUE, TRUE);
+ peer->mwipvt = dialog_unref(peer->mwipvt, "unreffing peer->mwipvt");
}
-
- res = 0;
- ast_set_flag(&p->flags[0], SIP_OUTGOING);
-
- /* T.38 re-INVITE FAX detection should never be done for outgoing calls,
- * so ensure it is disabled.
- */
- ast_clear_flag(&p->flags[1], SIP_PAGE2_FAX_DETECT_T38);
-
- if (p->options->transfer) {
- char buf[SIPBUFSIZE/2];
-
- if (referer) {
- if (sipdebug)
- ast_debug(3, "Call for %s transfered by %s\n", p->username, referer);
- snprintf(buf, sizeof(buf)-1, "-> %s (via %s)", p->cid_name, referer);
- } else
- snprintf(buf, sizeof(buf)-1, "-> %s", p->cid_name);
- ast_string_field_set(p, cid_name, buf);
+
+ if (peer->chanvars) {
+ ast_variables_destroy(peer->chanvars);
+ peer->chanvars = NULL;
}
- ast_debug(1, "Outgoing Call for %s\n", p->username);
-
- res = update_call_counter(p, INC_CALL_RINGING);
+
+ register_peer_exten(peer, FALSE);
+ ast_free_ha(peer->ha);
+ if (peer->selfdestruct)
+ ast_atomic_fetchadd_int(&apeerobjs, -1);
+ else if (peer->is_realtime) {
+ ast_atomic_fetchadd_int(&rpeerobjs, -1);
+ ast_debug(3, "-REALTIME- peer Destroyed. Name: %s. Realtime Peer objects: %d\n", peer->name, rpeerobjs);
+ } else
+ ast_atomic_fetchadd_int(&speerobjs, -1);
+ clear_realm_authentication(peer->auth);
+ peer->auth = NULL;
+ if (peer->dnsmgr)
+ ast_dnsmgr_release(peer->dnsmgr);
+ clear_peer_mailboxes(peer);
- if (res == -1) {
- ast->hangupcause = AST_CAUSE_USER_BUSY;
- return res;
+ if (peer->socket.tcptls_session) {
+ ao2_ref(peer->socket.tcptls_session, -1);
+ peer->socket.tcptls_session = NULL;
}
- p->callingpres = ast->cid.cid_pres;
- p->jointcapability = ast_rtp_instance_available_formats(p->rtp, p->capability, p->prefcodec);
- p->jointnoncodeccapability = p->noncodeccapability;
-
- /* If there are no audio formats left to offer, punt */
- if (!(p->jointcapability & AST_FORMAT_AUDIO_MASK)) {
- ast_log(LOG_WARNING, "No audio format found to offer. Cancelling call to %s\n", p->username);
- res = -1;
- } else {
- int xmitres;
- sip_pvt_lock(p);
- xmitres = transmit_invite(p, SIP_INVITE, 1, 2);
- sip_pvt_unlock(p);
- if (xmitres == XMIT_ERROR)
- return -1;
- p->invitestate = INV_CALLING;
+ ast_cc_config_params_destroy(peer->cc_params);
- /* Initialize auto-congest time */
- AST_SCHED_REPLACE_UNREF(p->initid, sched, p->timer_b, auto_congest, p,
- dialog_unref(_data, "dialog ptr dec when SCHED_REPLACE del op succeeded"),
- dialog_unref(p, "dialog ptr dec when SCHED_REPLACE add failed"),
- dialog_ref(p, "dialog ptr inc when SCHED_REPLACE add succeeded") );
- }
- return res;
+ ast_string_field_free_memory(peer);
}
-/*! \brief Destroy registry object
- Objects created with the register= statement in static configuration */
-static void sip_registry_destroy(struct sip_registry *reg)
+/*! \brief Update peer data in database (if used) */
+static void update_peer(struct sip_peer *p, int expire)
{
- /* Really delete */
- ast_debug(3, "Destroying registry entry for %s@%s\n", reg->username, reg->hostname);
-
- if (reg->call) {
- /* Clear registry before destroying to ensure
- we don't get reentered trying to grab the registry lock */
- reg->call->registry = registry_unref(reg->call->registry, "destroy reg->call->registry");
- ast_debug(3, "Destroying active SIP dialog for registry %s@%s\n", reg->username, reg->hostname);
- dialog_unlink_all(reg->call, TRUE, TRUE);
- reg->call = dialog_unref(reg->call, "unref reg->call");
- /* reg->call = sip_destroy(reg->call); */
+ int rtcachefriends = ast_test_flag(&p->flags[1], SIP_PAGE2_RTCACHEFRIENDS);
+ if (sip_cfg.peer_rtupdate &&
+ (p->is_realtime || rtcachefriends)) {
+ realtime_update_peer(p->name, &p->addr, p->username, rtcachefriends ? p->fullcontact : NULL, p->useragent, expire, p->deprecated_username, p->lastms);
}
- AST_SCHED_DEL(sched, reg->expire);
- AST_SCHED_DEL(sched, reg->timeout);
-
- ast_string_field_free_memory(reg);
- ast_atomic_fetchadd_int(®objs, -1);
- ast_dnsmgr_release(reg->dnsmgr);
- ast_free(reg);
}
-/*! \brief Destroy MWI subscription object */
-static void sip_subscribe_mwi_destroy(struct sip_subscription_mwi *mwi)
+static struct ast_variable *get_insecure_variable_from_config(struct ast_config *cfg)
{
- if (mwi->call) {
- mwi->call->mwi = NULL;
- sip_destroy(mwi->call);
+ struct ast_variable *var = NULL;
+ struct ast_flags flags = {0};
+ char *cat = NULL;
+ const char *insecure;
+ while ((cat = ast_category_browse(cfg, cat))) {
+ insecure = ast_variable_retrieve(cfg, cat, "insecure");
+ set_insecure_flags(&flags, insecure, -1);
+ if (ast_test_flag(&flags, SIP_INSECURE_PORT)) {
+ var = ast_category_root(cfg, cat);
+ break;
+ }
}
-
- AST_SCHED_DEL(sched, mwi->resub);
- ast_string_field_free_memory(mwi);
- ast_dnsmgr_release(mwi->dnsmgr);
- ast_free(mwi);
+ return var;
}
-/*! \brief Execute destruction of SIP dialog structure, release memory */
-void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist)
+static const char *get_name_from_variable(struct ast_variable *var, const char *newpeername)
{
- struct sip_request *req;
-
- if (p->stimer) {
- ast_free(p->stimer);
- p->stimer = NULL;
+ struct ast_variable *tmp;
+ for (tmp = var; tmp; tmp = tmp->next) {
+ if (!newpeername && !strcasecmp(tmp->name, "name"))
+ newpeername = tmp->value;
}
+ return newpeername;
+}
- if (sip_debug_test_pvt(p))
- ast_verbose("Really destroying SIP dialog '%s' Method: %s\n", p->callid, sip_methods[p->method].text);
-
- if (ast_test_flag(&p->flags[0], SIP_INC_COUNT) || ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD)) {
- update_call_counter(p, DEC_CALL_LIMIT);
- ast_debug(2, "This call did not properly clean up call limits. Call ID %s\n", p->callid);
- }
+/*! \brief realtime_peer: Get peer from realtime storage
+ * Checks the "sippeers" realtime family from extconfig.conf
+ * Checks the "sipregs" realtime family from extconfig.conf if it's configured.
+ * This returns a pointer to a peer and because we use build_peer, we can rest
+ * assured that the refcount is bumped.
+*/
+static struct sip_peer *realtime_peer(const char *newpeername, struct sockaddr_in *sin, int devstate_only)
+{
+ struct sip_peer *peer;
+ struct ast_variable *var = NULL;
+ struct ast_variable *varregs = NULL;
+ struct ast_variable *tmp;
+ struct ast_config *peerlist = NULL;
+ char ipaddr[INET_ADDRSTRLEN];
+ char portstring[6]; /*up to 5 digits plus null terminator*/
+ char *cat = NULL;
+ unsigned short portnum;
+ int realtimeregs = ast_check_realtime("sipregs");
- /* Unlink us from the owner if we have one */
- if (p->owner) {
- if (lockowner)
- ast_channel_lock(p->owner);
- if (option_debug)
- ast_log(LOG_DEBUG, "Detaching from %s\n", p->owner->name);
- p->owner->tech_pvt = NULL;
- /* Make sure that the channel knows its backend is going away */
- p->owner->_softhangup |= AST_SOFTHANGUP_DEV;
- if (lockowner)
- ast_channel_unlock(p->owner);
- /* Give the channel a chance to react before deallocation */
- usleep(1);
- }
+ /* First check on peer name */
+ if (newpeername) {
+ if (realtimeregs)
+ varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
- /* Remove link from peer to subscription of MWI */
- if (p->relatedpeer && p->relatedpeer->mwipvt)
- p->relatedpeer->mwipvt = dialog_unref(p->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt");
- if (p->relatedpeer && p->relatedpeer->call == p)
- p->relatedpeer->call = dialog_unref(p->relatedpeer->call, "unset the relatedpeer->call field in tandem with relatedpeer field itself");
-
- if (p->relatedpeer)
- p->relatedpeer = unref_peer(p->relatedpeer,"unsetting a dialog relatedpeer field in sip_destroy");
-
- if (p->registry) {
- if (p->registry->call == p)
- p->registry->call = dialog_unref(p->registry->call, "nulling out the registry's call dialog field in unlink_all");
- p->registry = registry_unref(p->registry, "delete p->registry");
- }
-
- if (p->mwi) {
- p->mwi->call = NULL;
+ var = ast_load_realtime("sippeers", "name", newpeername, "host", "dynamic", SENTINEL);
+ if (!var && sin)
+ var = ast_load_realtime("sippeers", "name", newpeername, "host", ast_inet_ntoa(sin->sin_addr), SENTINEL);
+ if (!var) {
+ var = ast_load_realtime("sippeers", "name", newpeername, SENTINEL);
+ /*!\note
+ * If this one loaded something, then we need to ensure that the host
+ * field matched. The only reason why we can't have this as a criteria
+ * is because we only have the IP address and the host field might be
+ * set as a name (and the reverse PTR might not match).
+ */
+ if (var && sin) {
+ for (tmp = var; tmp; tmp = tmp->next) {
+ if (!strcasecmp(tmp->name, "host")) {
+ struct hostent *hp;
+ struct ast_hostent ahp;
+ if (!(hp = ast_gethostbyname(tmp->value, &ahp)) || (memcmp(hp->h_addr, &sin->sin_addr, sizeof(hp->h_addr)))) {
+ /* No match */
+ ast_variables_destroy(var);
+ var = NULL;
+ }
+ break;
+ }
+ }
+ }
+ }
}
- if (dumphistory)
- sip_dump_history(p);
-
- if (p->options)
- ast_free(p->options);
-
- if (p->notify) {
- ast_variables_destroy(p->notify->headers);
- ast_free(p->notify->content);
- ast_free(p->notify);
- }
- if (p->rtp) {
- ast_rtp_instance_destroy(p->rtp);
+ if (!var && sin) { /* Then check on IP address for dynamic peers */
+ ast_copy_string(ipaddr, ast_inet_ntoa(sin->sin_addr), sizeof(ipaddr));
+ portnum = ntohs(sin->sin_port);
+ sprintf(portstring, "%u", portnum);
+ var = ast_load_realtime("sippeers", "host", ipaddr, "port", portstring, SENTINEL); /* First check for fixed IP hosts */
+ if (var) {
+ if (realtimeregs) {
+ newpeername = get_name_from_variable(var, newpeername);
+ varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
+ }
+ } else {
+ if (realtimeregs)
+ varregs = ast_load_realtime("sipregs", "ipaddr", ipaddr, "port", portstring, SENTINEL); /* Then check for registered hosts */
+ else
+ var = ast_load_realtime("sippeers", "ipaddr", ipaddr, "port", portstring, SENTINEL); /* Then check for registered hosts */
+ if (varregs) {
+ newpeername = get_name_from_variable(varregs, newpeername);
+ var = ast_load_realtime("sippeers", "name", newpeername, SENTINEL);
+ }
+ }
+ if (!var) { /*We couldn't match on ipaddress and port, so we need to check if port is insecure*/
+ peerlist = ast_load_realtime_multientry("sippeers", "host", ipaddr, SENTINEL);
+ if (peerlist) {
+ var = get_insecure_variable_from_config(peerlist);
+ if(var) {
+ if (realtimeregs) {
+ newpeername = get_name_from_variable(var, newpeername);
+ varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
+ }
+ } else { /*var wasn't found in the list of "hosts", so try "ipaddr"*/
+ peerlist = NULL;
+ cat = NULL;
+ peerlist = ast_load_realtime_multientry("sippeers", "ipaddr", ipaddr, SENTINEL);
+ if(peerlist) {
+ var = get_insecure_variable_from_config(peerlist);
+ if(var) {
+ if (realtimeregs) {
+ newpeername = get_name_from_variable(var, newpeername);
+ varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
+ }
+ }
+ }
+ }
+ } else {
+ if (realtimeregs) {
+ peerlist = ast_load_realtime_multientry("sipregs", "ipaddr", ipaddr, SENTINEL);
+ if (peerlist) {
+ varregs = get_insecure_variable_from_config(peerlist);
+ if (varregs) {
+ newpeername = get_name_from_variable(varregs, newpeername);
+ var = ast_load_realtime("sippeers", "name", newpeername, SENTINEL);
+ }
+ }
+ } else {
+ peerlist = ast_load_realtime_multientry("sippeers", "ipaddr", ipaddr, SENTINEL);
+ if (peerlist) {
+ var = get_insecure_variable_from_config(peerlist);
+ if (var) {
+ newpeername = get_name_from_variable(var, newpeername);
+ varregs = ast_load_realtime("sipregs", "name", newpeername, SENTINEL);
+ }
+ }
+ }
+ }
+ }
}
- if (p->vrtp) {
- ast_rtp_instance_destroy(p->vrtp);
+
+ if (!var) {
+ if (peerlist)
+ ast_config_destroy(peerlist);
+ return NULL;
}
- if (p->trtp) {
- ast_rtp_instance_destroy(p->trtp);
+
+ for (tmp = var; tmp; tmp = tmp->next) {
+ /* If this is type=user, then skip this object. */
+ if (!strcasecmp(tmp->name, "type") &&
+ !strcasecmp(tmp->value, "user")) {
+ if(peerlist)
+ ast_config_destroy(peerlist);
+ else {
+ ast_variables_destroy(var);
+ ast_variables_destroy(varregs);
+ }
+ return NULL;
+ } else if (!newpeername && !strcasecmp(tmp->name, "name")) {
+ newpeername = tmp->value;
+ }
}
- if (p->udptl)
- ast_udptl_destroy(p->udptl);
- if (p->refer)
- ast_free(p->refer);
- if (p->route) {
- free_old_route(p->route);
- p->route = NULL;
+
+ if (!newpeername) { /* Did not find peer in realtime */
+ ast_log(LOG_WARNING, "Cannot Determine peer name ip=%s\n", ipaddr);
+ if(peerlist)
+ ast_config_destroy(peerlist);
+ else
+ ast_variables_destroy(var);
+ return NULL;
}
- if (p->initreq.data)
- ast_free(p->initreq.data);
- /* Destroy Session-Timers if allocated */
- if (p->stimer) {
- p->stimer->quit_flag = 1;
- if (p->stimer->st_active == TRUE && p->stimer->st_schedid > -1) {
- AST_SCHED_DEL_UNREF(sched, p->stimer->st_schedid,
- dialog_unref(p, "removing session timer ref"));
+
+ /* Peer found in realtime, now build it in memory */
+ peer = build_peer(newpeername, var, varregs, TRUE, devstate_only);
+ if (!peer) {
+ if(peerlist)
+ ast_config_destroy(peerlist);
+ else {
+ ast_variables_destroy(var);
+ ast_variables_destroy(varregs);
}
- ast_free(p->stimer);
- p->stimer = NULL;
+ return NULL;
}
- /* Clear history */
- if (p->history) {
- struct sip_history *hist;
- while ( (hist = AST_LIST_REMOVE_HEAD(p->history, list)) ) {
- ast_free(hist);
- p->history_entries--;
+ ast_debug(3, "-REALTIME- loading peer from database to memory. Name: %s. Peer objects: %d\n", peer->name, rpeerobjs);
+
+ if (ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS) && !devstate_only) {
+ /* Cache peer */
+ ast_copy_flags(&peer->flags[1], &global_flags[1], SIP_PAGE2_RTAUTOCLEAR|SIP_PAGE2_RTCACHEFRIENDS);
+ if (ast_test_flag(&global_flags[1], SIP_PAGE2_RTAUTOCLEAR)) {
+ AST_SCHED_REPLACE_UNREF(peer->expire, sched, sip_cfg.rtautoclear * 1000, expire_register, peer,
+ unref_peer(_data, "remove registration ref"),
+ unref_peer(peer, "remove registration ref"),
+ ref_peer(peer, "add registration ref"));
+ }
+ ao2_t_link(peers, peer, "link peer into peers table");
+ if (peer->addr.sin_addr.s_addr) {
+ ao2_t_link(peers_by_ip, peer, "link peer into peers_by_ip table");
}
- ast_free(p->history);
- p->history = NULL;
}
-
- while ((req = AST_LIST_REMOVE_HEAD(&p->request_queue, next))) {
- ast_free(req);
+ peer->is_realtime = 1;
+ if (peerlist)
+ ast_config_destroy(peerlist);
+ else {
+ ast_variables_destroy(var);
+ ast_variables_destroy(varregs);
}
- if (p->chanvars) {
- ast_variables_destroy(p->chanvars);
- p->chanvars = NULL;
- }
+ return peer;
+}
- ast_string_field_free_memory(p);
+/* Function to assist finding peers by name only */
+static int find_by_name(void *obj, void *arg, void *data, int flags)
+{
+ struct sip_peer *search = obj, *match = arg;
+ int *which_objects = data;
- if (p->socket.tcptls_session) {
- ao2_ref(p->socket.tcptls_session, -1);
- p->socket.tcptls_session = NULL;
+ /* Usernames in SIP uri's are case sensitive. Domains are not */
+ if (strcmp(search->name, match->name)) {
+ return 0;
+ }
+
+ switch (*which_objects) {
+ case FINDUSERS:
+ if (!(search->type & SIP_TYPE_USER)) {
+ return 0;
+ }
+ break;
+ case FINDPEERS:
+ if (!(search->type & SIP_TYPE_PEER)) {
+ return 0;
+ }
+ break;
+ case FINDALLDEVICES:
+ break;
}
+
+ return CMP_MATCH | CMP_STOP;
}
-/*! \brief update_call_counter: Handle call_limit for SIP devices
- * Setting a call-limit will cause calls above the limit not to be accepted.
- *
- * Remember that for a type=friend, there's one limit for the user and
- * another for the peer, not a combined call limit.
- * This will cause unexpected behaviour in subscriptions, since a "friend"
- * is *two* devices in Asterisk, not one.
+/*!
+ * \brief Locate device by name or ip address
*
- * Thought: For realtime, we should probably update storage with inuse counter...
+ * \param which_objects Define which objects should be matched when doing a lookup
+ * by name. Valid options are FINDUSERS, FINDPEERS, or FINDALLDEVICES.
+ * Note that this option is not used at all when doing a lookup by IP.
*
- * \return 0 if call is ok (no call limit, below threshold)
- * -1 on rejection of call
+ * This is used on find matching device on name or ip/port.
+ * If the device was declared as type=peer, we don't match on peer name on incoming INVITEs.
*
+ * \note Avoid using this function in new functions if there is a way to avoid it,
+ * since it might cause a database lookup.
*/
-static int update_call_counter(struct sip_pvt *fup, int event)
+static struct sip_peer *find_peer(const char *peer, struct sockaddr_in *sin, int realtime, int which_objects, int devstate_only, int transport)
{
- char name[256];
- int *inuse = NULL, *call_limit = NULL, *inringing = NULL;
- int outgoing = fup->outgoing_call;
struct sip_peer *p = NULL;
+ struct sip_peer tmp_peer;
- ast_debug(3, "Updating call counter for %s call\n", outgoing ? "outgoing" : "incoming");
+ if (peer) {
+ ast_copy_string(tmp_peer.name, peer, sizeof(tmp_peer.name));
+ p = ao2_t_callback_data(peers, OBJ_POINTER, find_by_name, &tmp_peer, &which_objects, "ao2_find in peers table");
+ } else if (sin) { /* search by addr? */
+ tmp_peer.addr.sin_addr.s_addr = sin->sin_addr.s_addr;
+ tmp_peer.addr.sin_port = sin->sin_port;
+ tmp_peer.flags[0].flags = 0;
+ tmp_peer.transports = transport;
+ p = ao2_t_find(peers_by_ip, &tmp_peer, OBJ_POINTER, "ao2_find in peers_by_ip table"); /* WAS: p = ASTOBJ_CONTAINER_FIND_FULL(&peerl, sin, name, sip_addr_hashfunc, 1, sip_addrcmp); */
+ if (!p) {
+ ast_set_flag(&tmp_peer.flags[0], SIP_INSECURE_PORT);
+ p = ao2_t_find(peers_by_ip, &tmp_peer, OBJ_POINTER, "ao2_find in peers_by_ip table 2"); /* WAS: p = ASTOBJ_CONTAINER_FIND_FULL(&peerl, sin, name, sip_addr_hashfunc, 1, sip_addrcmp); */
+ if (p) {
+ return p;
+ }
+ }
+ }
+ if (!p && (realtime || devstate_only)) {
+ p = realtime_peer(peer, sin, devstate_only);
+ }
- /* Test if we need to check call limits, in order to avoid
- realtime lookups if we do not need it */
- if (!ast_test_flag(&fup->flags[0], SIP_CALL_LIMIT) && !ast_test_flag(&fup->flags[1], SIP_PAGE2_CALL_ONHOLD))
- return 0;
+ return p;
+}
- ast_copy_string(name, fup->username, sizeof(name));
+/*! \brief Set nat mode on the various data sockets */
+static void do_setnat(struct sip_pvt *p)
+{
+ const char *mode;
+ int natflags;
- /* Check the list of devices */
- if ((p = find_peer(ast_strlen_zero(fup->peername) ? name : fup->peername, NULL, TRUE, FINDALLDEVICES, FALSE, 0))) {
- inuse = &p->inUse;
- call_limit = &p->call_limit;
- inringing = &p->inRinging;
- ast_copy_string(name, fup->peername, sizeof(name));
- }
- if (!p) {
- ast_debug(2, "%s is not a local device, no call limit\n", name);
- return 0;
+ natflags = ast_test_flag(&p->flags[1], SIP_PAGE2_SYMMETRICRTP);
+ mode = natflags ? "On" : "Off";
+
+ if (p->rtp) {
+ ast_debug(1, "Setting NAT on RTP to %s\n", mode);
+ ast_rtp_instance_set_prop(p->rtp, AST_RTP_PROPERTY_NAT, natflags);
+ }
+ if (p->vrtp) {
+ ast_debug(1, "Setting NAT on VRTP to %s\n", mode);
+ ast_rtp_instance_set_prop(p->vrtp, AST_RTP_PROPERTY_NAT, natflags);
+ }
+ if (p->udptl) {
+ ast_debug(1, "Setting NAT on UDPTL to %s\n", mode);
+ ast_udptl_setnat(p->udptl, natflags);
}
+ if (p->trtp) {
+ ast_debug(1, "Setting NAT on TRTP to %s\n", mode);
+ ast_rtp_instance_set_prop(p->trtp, AST_RTP_PROPERTY_NAT, natflags);
+ }
+}
- switch(event) {
- /* incoming and outgoing affects the inUse counter */
- case DEC_CALL_LIMIT:
- /* Decrement inuse count if applicable */
- if (inuse) {
- sip_pvt_lock(fup);
- ao2_lock(p);
- if (*inuse > 0) {
- if (ast_test_flag(&fup->flags[0], SIP_INC_COUNT)) {
- (*inuse)--;
- ast_clear_flag(&fup->flags[0], SIP_INC_COUNT);
- }
- } else {
- *inuse = 0;
- }
- ao2_unlock(p);
- sip_pvt_unlock(fup);
- }
+/*! \brief Change the T38 state on a SIP dialog */
+static void change_t38_state(struct sip_pvt *p, int state)
+{
+ int old = p->t38.state;
+ struct ast_channel *chan = p->owner;
+ struct ast_control_t38_parameters parameters = { .request_response = 0 };
- /* Decrement ringing count if applicable */
- if (inringing) {
- sip_pvt_lock(fup);
- ao2_lock(p);
- if (*inringing > 0) {
- if (ast_test_flag(&fup->flags[0], SIP_INC_RINGING)) {
- (*inringing)--;
- ast_clear_flag(&fup->flags[0], SIP_INC_RINGING);
- }
- } else {
- *inringing = 0;
- }
- ao2_unlock(p);
- sip_pvt_unlock(fup);
- }
+ /* Don't bother changing if we are already in the state wanted */
+ if (old == state)
+ return;
- /* Decrement onhold count if applicable */
- sip_pvt_lock(fup);
- ao2_lock(p);
- if (ast_test_flag(&fup->flags[1], SIP_PAGE2_CALL_ONHOLD) && sip_cfg.notifyhold) {
- ast_clear_flag(&fup->flags[1], SIP_PAGE2_CALL_ONHOLD);
- ao2_unlock(p);
- sip_pvt_unlock(fup);
- sip_peer_hold(fup, FALSE);
- } else {
- ao2_unlock(p);
- sip_pvt_unlock(fup);
- }
- if (sipdebug)
- ast_debug(2, "Call %s %s '%s' removed from call limit %d\n", outgoing ? "to" : "from", "peer", name, *call_limit);
- break;
+ p->t38.state = state;
+ ast_debug(2, "T38 state changed to %d on channel %s\n", p->t38.state, chan ? chan->name : "<none>");
- case INC_CALL_RINGING:
- case INC_CALL_LIMIT:
- /* If call limit is active and we have reached the limit, reject the call */
- if (*call_limit > 0 ) {
- if (*inuse >= *call_limit) {
- ast_log(LOG_NOTICE, "Call %s %s '%s' rejected due to usage limit of %d\n", outgoing ? "to" : "from", "peer", name, *call_limit);
- unref_peer(p, "update_call_counter: unref peer p, call limit exceeded");
- return -1;
- }
- }
- if (inringing && (event == INC_CALL_RINGING)) {
- sip_pvt_lock(fup);
- ao2_lock(p);
- if (!ast_test_flag(&fup->flags[0], SIP_INC_RINGING)) {
- (*inringing)++;
- ast_set_flag(&fup->flags[0], SIP_INC_RINGING);
- }
- ao2_unlock(p);
- sip_pvt_unlock(fup);
- }
- if (inuse) {
- sip_pvt_lock(fup);
- ao2_lock(p);
- if (!ast_test_flag(&fup->flags[0], SIP_INC_COUNT)) {
- (*inuse)++;
- ast_set_flag(&fup->flags[0], SIP_INC_COUNT);
- }
- ao2_unlock(p);
- sip_pvt_unlock(fup);
- }
- if (sipdebug) {
- ast_debug(2, "Call %s %s '%s' is %d out of %d\n", outgoing ? "to" : "from", "peer", name, *inuse, *call_limit);
- }
- break;
+ /* If no channel was provided we can't send off a control frame */
+ if (!chan)
+ return;
- case DEC_CALL_RINGING:
- if (inringing) {
- sip_pvt_lock(fup);
- ao2_lock(p);
- if (ast_test_flag(&fup->flags[0], SIP_INC_RINGING)) {
- if (*inringing > 0) {
- (*inringing)--;
- }
- ast_clear_flag(&fup->flags[0], SIP_INC_RINGING);
- }
- ao2_unlock(p);
- sip_pvt_unlock(fup);
+ /* Given the state requested and old state determine what control frame we want to queue up */
+ switch (state) {
+ case T38_PEER_REINVITE:
+ parameters = p->t38.their_parms;
+ parameters.max_ifp = ast_udptl_get_far_max_ifp(p->udptl);
+ parameters.request_response = AST_T38_REQUEST_NEGOTIATE;
+ ast_udptl_set_tag(p->udptl, "SIP/%s", p->username);
+ break;
+ case T38_ENABLED:
+ parameters = p->t38.their_parms;
+ parameters.max_ifp = ast_udptl_get_far_max_ifp(p->udptl);
+ parameters.request_response = AST_T38_NEGOTIATED;
+ ast_udptl_set_tag(p->udptl, "SIP/%s", p->username);
+ break;
+ case T38_DISABLED:
+ if (old == T38_ENABLED) {
+ parameters.request_response = AST_T38_TERMINATED;
+ } else if (old == T38_LOCAL_REINVITE) {
+ parameters.request_response = AST_T38_REFUSED;
}
break;
-
- default:
- ast_log(LOG_ERROR, "update_call_counter(%s, %d) called with no event!\n", name, event);
+ case T38_LOCAL_REINVITE:
+ /* wait until we get a peer response before responding to local reinvite */
+ break;
}
- if (p) {
- ast_devstate_changed(AST_DEVICE_UNKNOWN, "SIP/%s", p->name);
- unref_peer(p, "update_call_counter: unref_peer from call counter");
- }
- return 0;
+ /* Woot we got a message, create a control frame and send it on! */
+ if (parameters.request_response)
+ ast_queue_control_data(chan, AST_CONTROL_T38_PARAMETERS, ¶meters, sizeof(parameters));
}
+/*! \brief Set the global T38 capabilities on a SIP dialog structure */
+static void set_t38_capabilities(struct sip_pvt *p)
+{
+ if (p->udptl) {
+ if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) == SIP_PAGE2_T38SUPPORT_UDPTL_REDUNDANCY) {
+ ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_REDUNDANCY);
+ } else if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) == SIP_PAGE2_T38SUPPORT_UDPTL_FEC) {
+ ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_FEC);
+ } else if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT) == SIP_PAGE2_T38SUPPORT_UDPTL) {
+ ast_udptl_set_error_correction_scheme(p->udptl, UDPTL_ERROR_CORRECTION_NONE);
+ }
+ }
+}
-static void sip_destroy_fn(void *p)
+static void copy_socket_data(struct sip_socket *to_sock, const struct sip_socket *from_sock)
{
- sip_destroy(p);
+ if (to_sock->tcptls_session) {
+ ao2_ref(to_sock->tcptls_session, -1);
+ to_sock->tcptls_session = NULL;
+ }
+
+ if (from_sock->tcptls_session) {
+ ao2_ref(from_sock->tcptls_session, +1);
+ }
+
+ *to_sock = *from_sock;
}
-/*! \brief Destroy SIP call structure.
- * Make it return NULL so the caller can do things like
- * foo = sip_destroy(foo);
- * and reduce the chance of bugs due to dangling pointers.
+/*! \brief Initialize RTP portion of a dialog
+ * \return -1 on failure, 0 on success
*/
-struct sip_pvt *sip_destroy(struct sip_pvt *p)
+static int dialog_initialize_rtp(struct sip_pvt *dialog)
{
- ast_debug(3, "Destroying SIP dialog %s\n", p->callid);
- __sip_destroy(p, TRUE, TRUE);
- return NULL;
+ if (!sip_methods[dialog->method].need_rtp) {
+ return 0;
+ }
+
+ if (!(dialog->rtp = ast_rtp_instance_new(dialog->engine, sched, &bindaddr, NULL))) {
+ return -1;
+ }
+
+ if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_VIDEOSUPPORT) && (dialog->capability & AST_FORMAT_VIDEO_MASK)) {
+ if (!(dialog->vrtp = ast_rtp_instance_new(dialog->engine, sched, &bindaddr, NULL))) {
+ return -1;
+ }
+ ast_rtp_instance_set_timeout(dialog->vrtp, global_rtptimeout);
+ ast_rtp_instance_set_hold_timeout(dialog->vrtp, global_rtpholdtimeout);
+
+ ast_rtp_instance_set_prop(dialog->vrtp, AST_RTP_PROPERTY_RTCP, 1);
+ }
+
+ if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_TEXTSUPPORT)) {
+ if (!(dialog->trtp = ast_rtp_instance_new(dialog->engine, sched, &bindaddr, NULL))) {
+ return -1;
+ }
+ ast_rtp_instance_set_timeout(dialog->trtp, global_rtptimeout);
+ ast_rtp_instance_set_hold_timeout(dialog->trtp, global_rtpholdtimeout);
+
+ ast_rtp_instance_set_prop(dialog->trtp, AST_RTP_PROPERTY_RTCP, 1);
+ }
+
+ ast_rtp_instance_set_timeout(dialog->rtp, global_rtptimeout);
+ ast_rtp_instance_set_hold_timeout(dialog->rtp, global_rtpholdtimeout);
+
+ ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_RTCP, 1);
+ ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF, ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833);
+ ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, ast_test_flag(&dialog->flags[1], SIP_PAGE2_RFC2833_COMPENSATE));
+
+ ast_rtp_instance_set_qos(dialog->rtp, global_tos_audio, 0, "SIP RTP");
+
+ do_setnat(dialog);
+
+ return 0;
}
-/*! \brief Convert SIP hangup causes to Asterisk hangup causes */
-int hangup_sip2cause(int cause)
+/*! \brief Create address structure from peer reference.
+ * This function copies data from peer to the dialog, so we don't have to look up the peer
+ * again from memory or database during the life time of the dialog.
+ *
+ * \return -1 on error, 0 on success.
+ *
+ */
+static int create_addr_from_peer(struct sip_pvt *dialog, struct sip_peer *peer)
{
- /* Possible values taken from causes.h */
- switch(cause) {
- case 401: /* Unauthorized */
- return AST_CAUSE_CALL_REJECTED;
- case 403: /* Not found */
- return AST_CAUSE_CALL_REJECTED;
- case 404: /* Not found */
+ /* this checks that the dialog is contacting the peer on a valid
+ * transport type based on the peers transport configuration,
+ * otherwise, this function bails out */
+ if (dialog->socket.type && check_request_transport(peer, dialog))
+ return -1;
+ copy_socket_data(&dialog->socket, &peer->socket);
+
+ if ((peer->addr.sin_addr.s_addr || peer->defaddr.sin_addr.s_addr) &&
+ (!peer->maxms || ((peer->lastms >= 0) && (peer->lastms <= peer->maxms)))) {
+ dialog->sa = (peer->addr.sin_addr.s_addr) ? peer->addr : peer->defaddr;
+ dialog->recv = dialog->sa;
+ } else
+ return -1;
+
+ ast_copy_flags(&dialog->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY);
+ ast_copy_flags(&dialog->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY);
+ dialog->capability = peer->capability;
+ dialog->prefs = peer->prefs;
+ if (ast_test_flag(&dialog->flags[1], SIP_PAGE2_T38SUPPORT)) {
+ if (!dialog->udptl) {
+ /* t38pt_udptl was enabled in the peer and not in [general] */
+ dialog->udptl = ast_udptl_new_with_bindaddr(sched, io, 0, bindaddr.sin_addr);
+ }
+ dialog->t38_maxdatagram = peer->t38_maxdatagram;
+ set_t38_capabilities(dialog);
+ } else if (dialog->udptl) {
+ ast_udptl_destroy(dialog->udptl);
+ dialog->udptl = NULL;
+ }
+
+ ast_string_field_set(dialog, engine, peer->engine);
+
+ if (dialog_initialize_rtp(dialog)) {
+ return -1;
+ }
+
+ if (dialog->rtp) { /* Audio */
+ ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF, ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833);
+ ast_rtp_instance_set_prop(dialog->rtp, AST_RTP_PROPERTY_DTMF_COMPENSATE, ast_test_flag(&dialog->flags[1], SIP_PAGE2_RFC2833_COMPENSATE));
+ ast_rtp_instance_set_timeout(dialog->rtp, peer->rtptimeout);
+ ast_rtp_instance_set_hold_timeout(dialog->rtp, peer->rtpholdtimeout);
+ /* Set Frame packetization */
+ ast_rtp_codecs_packetization_set(ast_rtp_instance_get_codecs(dialog->rtp), dialog->rtp, &dialog->prefs);
+ dialog->autoframing = peer->autoframing;
+ }
+ if (dialog->vrtp) { /* Video */
+ ast_rtp_instance_set_timeout(dialog->vrtp, peer->rtptimeout);
+ ast_rtp_instance_set_hold_timeout(dialog->vrtp, peer->rtpholdtimeout);
+ }
+ if (dialog->trtp) { /* Realtime text */
+ ast_rtp_instance_set_timeout(dialog->trtp, peer->rtptimeout);
+ ast_rtp_instance_set_hold_timeout(dialog->trtp, peer->rtpholdtimeout);
+ }
+
+ ast_string_field_set(dialog, peername, peer->name);
+ ast_string_field_set(dialog, authname, peer->username);
+ ast_string_field_set(dialog, username, peer->username);
+ ast_string_field_set(dialog, peersecret, peer->secret);
+ ast_string_field_set(dialog, peermd5secret, peer->md5secret);
+ ast_string_field_set(dialog, mohsuggest, peer->mohsuggest);
+ ast_string_field_set(dialog, mohinterpret, peer->mohinterpret);
+ ast_string_field_set(dialog, tohost, peer->tohost);
+ ast_string_field_set(dialog, fullcontact, peer->fullcontact);
+ ast_string_field_set(dialog, accountcode, peer->accountcode);
+ ast_string_field_set(dialog, context, peer->context);
+ ast_string_field_set(dialog, cid_num, peer->cid_num);
+ ast_string_field_set(dialog, cid_name, peer->cid_name);
+ ast_string_field_set(dialog, mwi_from, peer->mwi_from);
+ ast_string_field_set(dialog, parkinglot, peer->parkinglot);
+ ast_string_field_set(dialog, engine, peer->engine);
+ ref_proxy(dialog, obproxy_get(dialog, peer));
+ dialog->callgroup = peer->callgroup;
+ dialog->pickupgroup = peer->pickupgroup;
+ dialog->allowtransfer = peer->allowtransfer;
+ dialog->jointnoncodeccapability = dialog->noncodeccapability;
+ dialog->rtptimeout = peer->rtptimeout;
+ dialog->peerauth = peer->auth;
+ dialog->maxcallbitrate = peer->maxcallbitrate;
+ dialog->disallowed_methods = peer->disallowed_methods;
+ ast_cc_copy_config_params(dialog->cc_params, peer->cc_params);
+ if (ast_strlen_zero(dialog->tohost))
+ ast_string_field_set(dialog, tohost, ast_inet_ntoa(dialog->sa.sin_addr));
+ if (!ast_strlen_zero(peer->fromdomain)) {
+ ast_string_field_set(dialog, fromdomain, peer->fromdomain);
+ if (!dialog->initreq.headers) {
+ char *c;
+ char *tmpcall = ast_strdupa(dialog->callid);
+ /* this sure looks to me like we are going to change the callid on this dialog!! */
+ c = strchr(tmpcall, '@');
+ if (c) {
+ *c = '\0';
+ ao2_t_unlink(dialogs, dialog, "About to change the callid -- remove the old name");
+ ast_string_field_build(dialog, callid, "%s@%s", tmpcall, peer->fromdomain);
+ ao2_t_link(dialogs, dialog, "New dialog callid -- inserted back into table");
+ }
+ }
+ }
+ if (!ast_strlen_zero(peer->fromuser))
+ ast_string_field_set(dialog, fromuser, peer->fromuser);
+ if (!ast_strlen_zero(peer->language))
+ ast_string_field_set(dialog, language, peer->language);
+ /* Set timer T1 to RTT for this peer (if known by qualify=) */
+ /* Minimum is settable or default to 100 ms */
+ /* If there is a maxms and lastms from a qualify use that over a manual T1
+ value. Otherwise, use the peer's T1 value. */
+ if (peer->maxms && peer->lastms)
+ dialog->timer_t1 = peer->lastms < global_t1min ? global_t1min : peer->lastms;
+ else
+ dialog->timer_t1 = peer->timer_t1;
+
+ /* Set timer B to control transaction timeouts, the peer setting is the default and overrides
+ the known timer */
+ if (peer->timer_b)
+ dialog->timer_b = peer->timer_b;
+ else
+ dialog->timer_b = 64 * dialog->timer_t1;
+
+ if ((ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_RFC2833) ||
+ (ast_test_flag(&dialog->flags[0], SIP_DTMF) == SIP_DTMF_AUTO))
+ dialog->noncodeccapability |= AST_RTP_DTMF;
+ else
+ dialog->noncodeccapability &= ~AST_RTP_DTMF;
+ if (peer->call_limit)
+ ast_set_flag(&dialog->flags[0], SIP_CALL_LIMIT);
+ if (!dialog->portinuri)
+ dialog->portinuri = peer->portinuri;
+
+ dialog->chanvars = copy_vars(peer->chanvars);
+
+ return 0;
+}
+
+/*! \brief create address structure from device name
+ * Or, if peer not found, find it in the global DNS
+ * returns TRUE (-1) on failure, FALSE on success */
+static int create_addr(struct sip_pvt *dialog, const char *opeer, struct sockaddr_in *sin, int newdialog, struct sockaddr_in *remote_address)
+{
+ struct hostent *hp;
+ struct ast_hostent ahp;
+ struct sip_peer *peer;
+ char *port;
+ int portno = 0;
+ char host[MAXHOSTNAMELEN], *hostn;
+ char peername[256];
+ int srv_ret = 0;
+
+ ast_copy_string(peername, opeer, sizeof(peername));
+ port = strchr(peername, ':');
+ if (port) {
+ *port++ = '\0';
+ dialog->portinuri = 1;
+ }
+ dialog->sa.sin_family = AF_INET;
+ dialog->timer_t1 = global_t1; /* Default SIP retransmission timer T1 (RFC 3261) */
+ dialog->timer_b = global_timer_b; /* Default SIP transaction timer B (RFC 3261) */
+ peer = find_peer(peername, NULL, TRUE, FINDPEERS, FALSE, 0);
+
+ if (peer) {
+ int res;
+ if (newdialog) {
+ set_socket_transport(&dialog->socket, 0);
+ }
+ res = create_addr_from_peer(dialog, peer);
+ if (remote_address && remote_address->sin_addr.s_addr) {
+ dialog->sa = dialog->recv = *remote_address;
+ } else if (!ast_strlen_zero(port)) {
+ if ((portno = atoi(port))) {
+ dialog->sa.sin_port = dialog->recv.sin_port = htons(portno);
+ }
+ }
+ unref_peer(peer, "create_addr: unref peer from find_peer hashtab lookup");
+ return res;
+ }
+
+ if (dialog_initialize_rtp(dialog)) {
+ return -1;
+ }
+
+ ast_string_field_set(dialog, tohost, peername);
+ dialog->allowed_methods &= ~sip_cfg.disallowed_methods;
+
+ /* Get the outbound proxy information */
+ ref_proxy(dialog, obproxy_get(dialog, NULL));
+
+ if (sin) {
+ /* This address should be updated using dnsmgr */
+ memcpy(&dialog->sa.sin_addr, &sin->sin_addr, sizeof(dialog->sa.sin_addr));
+ if (!sin->sin_port) {
+ portno = port_str2int(port, (dialog->socket.type == SIP_TRANSPORT_TLS) ? STANDARD_TLS_PORT : STANDARD_SIP_PORT);
+ } else {
+ portno = ntohs(sin->sin_port);
+ }
+ } else {
+
+ /* Let's see if we can find the host in DNS. First try DNS SRV records,
+ then hostname lookup */
+ /*! \todo Fix this function. When we ask for SRV, we should check all transports
+ In the future, we should first check NAPTR to find out transport preference
+ */
+ hostn = peername;
+ /* Section 4.2 of RFC 3263 specifies that if a port number is specified, then
+ * an A record lookup should be used instead of SRV.
+ */
+ if (!port && sip_cfg.srvlookup) {
+ char service[MAXHOSTNAMELEN];
+ int tportno;
+
+ snprintf(service, sizeof(service), "_sip._%s.%s", get_transport(dialog->socket.type), peername);
+ srv_ret = ast_get_srv(NULL, host, sizeof(host), &tportno, service);
+ if (srv_ret > 0) {
+ hostn = host;
+ portno = tportno;
+ }
+ }
+ if (!portno)
+ portno = port_str2int(port, (dialog->socket.type == SIP_TRANSPORT_TLS) ? STANDARD_TLS_PORT : STANDARD_SIP_PORT);
+ hp = ast_gethostbyname(hostn, &ahp);
+ if (!hp) {
+ ast_log(LOG_WARNING, "No such host: %s\n", peername);
+ return -1;
+ }
+ memcpy(&dialog->sa.sin_addr, hp->h_addr, sizeof(dialog->sa.sin_addr));
+ }
+
+ if (!dialog->socket.type)
+ set_socket_transport(&dialog->socket, SIP_TRANSPORT_UDP);
+ if (!dialog->socket.port)
+ dialog->socket.port = bindaddr.sin_port;
+ dialog->sa.sin_port = htons(portno);
+ dialog->recv = dialog->sa;
+ return 0;
+}
+
+/*! \brief Scheduled congestion on a call.
+ * Only called by the scheduler, must return the reference when done.
+ */
+static int auto_congest(const void *arg)
+{
+ struct sip_pvt *p = (struct sip_pvt *)arg;
+
+ sip_pvt_lock(p);
+ p->initid = -1; /* event gone, will not be rescheduled */
+ if (p->owner) {
+ /* XXX fails on possible deadlock */
+ if (!ast_channel_trylock(p->owner)) {
+ append_history(p, "Cong", "Auto-congesting (timer)");
+ ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
+ ast_channel_unlock(p->owner);
+ }
+
+ /* Give the channel a chance to act before we proceed with destruction */
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ }
+ sip_pvt_unlock(p);
+ dialog_unref(p, "unreffing arg passed into auto_congest callback (p->initid)");
+ return 0;
+}
+
+
+/*! \brief Initiate SIP call from PBX
+ * used from the dial() application */
+static int sip_call(struct ast_channel *ast, char *dest, int timeout)
+{
+ int res;
+ struct sip_pvt *p = ast->tech_pvt; /* chan is locked, so the reference cannot go away */
+ struct varshead *headp;
+ struct ast_var_t *current;
+ const char *referer = NULL; /* SIP referrer */
+ int cc_core_id;
+ char uri[SIPBUFSIZE] = "";
+
+ if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
+ ast_log(LOG_WARNING, "sip_call called on %s, neither down nor reserved\n", ast->name);
+ return -1;
+ }
+
+ if (ast_cc_is_recall(ast, &cc_core_id, "SIP")) {
+ char device_name[AST_CHANNEL_NAME];
+ struct ast_cc_monitor *recall_monitor;
+ struct sip_monitor_instance *monitor_instance;
+ ast_channel_get_device_name(ast, device_name, sizeof(device_name));
+ if ((recall_monitor = ast_cc_get_monitor_by_recall_core_id(cc_core_id, device_name))) {
+ monitor_instance = recall_monitor->private_data;
+ ast_copy_string(uri, monitor_instance->notify_uri, sizeof(uri));
+ ao2_t_ref(recall_monitor, -1, "Got the URI we need so unreffing monitor");
+ }
+ }
+
+ /* Check whether there is vxml_url, distinctive ring variables */
+ headp=&ast->varshead;
+ AST_LIST_TRAVERSE(headp, current, entries) {
+ /* Check whether there is a VXML_URL variable */
+ if (!p->options->vxml_url && !strcasecmp(ast_var_name(current), "VXML_URL")) {
+ p->options->vxml_url = ast_var_value(current);
+ } else if (!p->options->uri_options && !strcasecmp(ast_var_name(current), "SIP_URI_OPTIONS")) {
+ p->options->uri_options = ast_var_value(current);
+ } else if (!p->options->addsipheaders && !strncasecmp(ast_var_name(current), "SIPADDHEADER", strlen("SIPADDHEADER"))) {
+ /* Check whether there is a variable with a name starting with SIPADDHEADER */
+ p->options->addsipheaders = 1;
+ } else if (!strcasecmp(ast_var_name(current), "SIPFROMDOMAIN")) {
+ ast_string_field_set(p, fromdomain, ast_var_value(current));
+ } else if (!strcasecmp(ast_var_name(current), "SIPTRANSFER")) {
+ /* This is a transfered call */
+ p->options->transfer = 1;
+ } else if (!strcasecmp(ast_var_name(current), "SIPTRANSFER_REFERER")) {
+ /* This is the referrer */
+ referer = ast_var_value(current);
+ } else if (!strcasecmp(ast_var_name(current), "SIPTRANSFER_REPLACES")) {
+ /* We're replacing a call. */
+ p->options->replaces = ast_var_value(current);
+ }
+ }
+
+ res = 0;
+ ast_set_flag(&p->flags[0], SIP_OUTGOING);
+
+ /* T.38 re-INVITE FAX detection should never be done for outgoing calls,
+ * so ensure it is disabled.
+ */
+ ast_clear_flag(&p->flags[1], SIP_PAGE2_FAX_DETECT_T38);
+
+ if (p->options->transfer) {
+ char buf[SIPBUFSIZE/2];
+
+ if (referer) {
+ if (sipdebug)
+ ast_debug(3, "Call for %s transfered by %s\n", p->username, referer);
+ snprintf(buf, sizeof(buf)-1, "-> %s (via %s)", p->cid_name, referer);
+ } else
+ snprintf(buf, sizeof(buf)-1, "-> %s", p->cid_name);
+ ast_string_field_set(p, cid_name, buf);
+ }
+ ast_debug(1, "Outgoing Call for %s\n", p->username);
+
+ res = update_call_counter(p, INC_CALL_RINGING);
+
+ if (res == -1) {
+ ast->hangupcause = AST_CAUSE_USER_BUSY;
+ return res;
+ }
+ p->callingpres = ast->cid.cid_pres;
+ p->jointcapability = ast_rtp_instance_available_formats(p->rtp, p->capability, p->prefcodec);
+ p->jointnoncodeccapability = p->noncodeccapability;
+
+ /* If there are no audio formats left to offer, punt */
+ if (!(p->jointcapability & AST_FORMAT_AUDIO_MASK)) {
+ ast_log(LOG_WARNING, "No audio format found to offer. Cancelling call to %s\n", p->username);
+ res = -1;
+ } else {
+ int xmitres;
+
+ sip_pvt_lock(p);
+ xmitres = transmit_invite(p, SIP_INVITE, 1, 2, uri);
+ sip_pvt_unlock(p);
+ if (xmitres == XMIT_ERROR)
+ return -1;
+ p->invitestate = INV_CALLING;
+
+ /* Initialize auto-congest time */
+ AST_SCHED_REPLACE_UNREF(p->initid, sched, p->timer_b, auto_congest, p,
+ dialog_unref(_data, "dialog ptr dec when SCHED_REPLACE del op succeeded"),
+ dialog_unref(p, "dialog ptr dec when SCHED_REPLACE add failed"),
+ dialog_ref(p, "dialog ptr inc when SCHED_REPLACE add succeeded") );
+ }
+ return res;
+}
+
+/*! \brief Destroy registry object
+ Objects created with the register= statement in static configuration */
+static void sip_registry_destroy(struct sip_registry *reg)
+{
+ /* Really delete */
+ ast_debug(3, "Destroying registry entry for %s@%s\n", reg->username, reg->hostname);
+
+ if (reg->call) {
+ /* Clear registry before destroying to ensure
+ we don't get reentered trying to grab the registry lock */
+ reg->call->registry = registry_unref(reg->call->registry, "destroy reg->call->registry");
+ ast_debug(3, "Destroying active SIP dialog for registry %s@%s\n", reg->username, reg->hostname);
+ dialog_unlink_all(reg->call, TRUE, TRUE);
+ reg->call = dialog_unref(reg->call, "unref reg->call");
+ /* reg->call = sip_destroy(reg->call); */
+ }
+ AST_SCHED_DEL(sched, reg->expire);
+ AST_SCHED_DEL(sched, reg->timeout);
+
+ ast_string_field_free_memory(reg);
+ ast_atomic_fetchadd_int(®objs, -1);
+ ast_dnsmgr_release(reg->dnsmgr);
+ ast_free(reg);
+}
+
+/*! \brief Destroy MWI subscription object */
+static void sip_subscribe_mwi_destroy(struct sip_subscription_mwi *mwi)
+{
+ if (mwi->call) {
+ mwi->call->mwi = NULL;
+ sip_destroy(mwi->call);
+ }
+
+ AST_SCHED_DEL(sched, mwi->resub);
+ ast_string_field_free_memory(mwi);
+ ast_dnsmgr_release(mwi->dnsmgr);
+ ast_free(mwi);
+}
+
+/*! \brief Execute destruction of SIP dialog structure, release memory */
+void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist)
+{
+ struct sip_request *req;
+
+ if (p->stimer) {
+ ast_free(p->stimer);
+ p->stimer = NULL;
+ }
+
+ if (sip_debug_test_pvt(p))
+ ast_verbose("Really destroying SIP dialog '%s' Method: %s\n", p->callid, sip_methods[p->method].text);
+
+ if (ast_test_flag(&p->flags[0], SIP_INC_COUNT) || ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD)) {
+ update_call_counter(p, DEC_CALL_LIMIT);
+ ast_debug(2, "This call did not properly clean up call limits. Call ID %s\n", p->callid);
+ }
+
+ /* Unlink us from the owner if we have one */
+ if (p->owner) {
+ if (lockowner)
+ ast_channel_lock(p->owner);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Detaching from %s\n", p->owner->name);
+ p->owner->tech_pvt = NULL;
+ /* Make sure that the channel knows its backend is going away */
+ p->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+ if (lockowner)
+ ast_channel_unlock(p->owner);
+ /* Give the channel a chance to react before deallocation */
+ usleep(1);
+ }
+
+ /* Remove link from peer to subscription of MWI */
+ if (p->relatedpeer && p->relatedpeer->mwipvt)
+ p->relatedpeer->mwipvt = dialog_unref(p->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt");
+ if (p->relatedpeer && p->relatedpeer->call == p)
+ p->relatedpeer->call = dialog_unref(p->relatedpeer->call, "unset the relatedpeer->call field in tandem with relatedpeer field itself");
+
+ if (p->relatedpeer)
+ p->relatedpeer = unref_peer(p->relatedpeer,"unsetting a dialog relatedpeer field in sip_destroy");
+
+ if (p->registry) {
+ if (p->registry->call == p)
+ p->registry->call = dialog_unref(p->registry->call, "nulling out the registry's call dialog field in unlink_all");
+ p->registry = registry_unref(p->registry, "delete p->registry");
+ }
+
+ if (p->mwi) {
+ p->mwi->call = NULL;
+ }
+
+ if (dumphistory)
+ sip_dump_history(p);
+
+ if (p->options)
+ ast_free(p->options);
+
+ if (p->notify) {
+ ast_variables_destroy(p->notify->headers);
+ ast_free(p->notify->content);
+ ast_free(p->notify);
+ }
+ if (p->rtp) {
+ ast_rtp_instance_destroy(p->rtp);
+ }
+ if (p->vrtp) {
+ ast_rtp_instance_destroy(p->vrtp);
+ }
+ if (p->trtp) {
+ ast_rtp_instance_destroy(p->trtp);
+ }
+ if (p->udptl)
+ ast_udptl_destroy(p->udptl);
+ if (p->refer)
+ ast_free(p->refer);
+ if (p->route) {
+ free_old_route(p->route);
+ p->route = NULL;
+ }
+ if (p->initreq.data)
+ ast_free(p->initreq.data);
+
+ /* Destroy Session-Timers if allocated */
+ if (p->stimer) {
+ p->stimer->quit_flag = 1;
+ if (p->stimer->st_active == TRUE && p->stimer->st_schedid > -1) {
+ AST_SCHED_DEL_UNREF(sched, p->stimer->st_schedid,
+ dialog_unref(p, "removing session timer ref"));
+ }
+ ast_free(p->stimer);
+ p->stimer = NULL;
+ }
+
+ /* Clear history */
+ if (p->history) {
+ struct sip_history *hist;
+ while ( (hist = AST_LIST_REMOVE_HEAD(p->history, list)) ) {
+ ast_free(hist);
+ p->history_entries--;
+ }
+ ast_free(p->history);
+ p->history = NULL;
+ }
+
+ while ((req = AST_LIST_REMOVE_HEAD(&p->request_queue, next))) {
+ ast_free(req);
+ }
+
+ if (p->chanvars) {
+ ast_variables_destroy(p->chanvars);
+ p->chanvars = NULL;
+ }
+
+ ast_string_field_free_memory(p);
+
+ ast_cc_config_params_destroy(p->cc_params);
+
+ if (p->epa_entry) {
+ ao2_ref(p->epa_entry, -1);
+ p->epa_entry = NULL;
+ }
+
+ if (p->socket.tcptls_session) {
+ ao2_ref(p->socket.tcptls_session, -1);
+ p->socket.tcptls_session = NULL;
+ }
+}
+
+/*! \brief update_call_counter: Handle call_limit for SIP devices
+ * Setting a call-limit will cause calls above the limit not to be accepted.
+ *
+ * Remember that for a type=friend, there's one limit for the user and
+ * another for the peer, not a combined call limit.
+ * This will cause unexpected behaviour in subscriptions, since a "friend"
+ * is *two* devices in Asterisk, not one.
+ *
+ * Thought: For realtime, we should probably update storage with inuse counter...
+ *
+ * \return 0 if call is ok (no call limit, below threshold)
+ * -1 on rejection of call
+ *
+ */
+static int update_call_counter(struct sip_pvt *fup, int event)
+{
+ char name[256];
+ int *inuse = NULL, *call_limit = NULL, *inringing = NULL;
+ int outgoing = fup->outgoing_call;
+ struct sip_peer *p = NULL;
+
+ ast_debug(3, "Updating call counter for %s call\n", outgoing ? "outgoing" : "incoming");
+
+
+ /* Test if we need to check call limits, in order to avoid
+ realtime lookups if we do not need it */
+ if (!ast_test_flag(&fup->flags[0], SIP_CALL_LIMIT) && !ast_test_flag(&fup->flags[1], SIP_PAGE2_CALL_ONHOLD))
+ return 0;
+
+ ast_copy_string(name, fup->username, sizeof(name));
+
+ /* Check the list of devices */
+ if ((p = find_peer(ast_strlen_zero(fup->peername) ? name : fup->peername, NULL, TRUE, FINDALLDEVICES, FALSE, 0))) {
+ inuse = &p->inUse;
+ call_limit = &p->call_limit;
+ inringing = &p->inRinging;
+ ast_copy_string(name, fup->peername, sizeof(name));
+ }
+ if (!p) {
+ ast_debug(2, "%s is not a local device, no call limit\n", name);
+ return 0;
+ }
+
+ switch(event) {
+ /* incoming and outgoing affects the inUse counter */
+ case DEC_CALL_LIMIT:
+ /* Decrement inuse count if applicable */
+ if (inuse) {
+ sip_pvt_lock(fup);
+ ao2_lock(p);
+ if (*inuse > 0) {
+ if (ast_test_flag(&fup->flags[0], SIP_INC_COUNT)) {
+ (*inuse)--;
+ ast_clear_flag(&fup->flags[0], SIP_INC_COUNT);
+ }
+ } else {
+ *inuse = 0;
+ }
+ ao2_unlock(p);
+ sip_pvt_unlock(fup);
+ }
+
+ /* Decrement ringing count if applicable */
+ if (inringing) {
+ sip_pvt_lock(fup);
+ ao2_lock(p);
+ if (*inringing > 0) {
+ if (ast_test_flag(&fup->flags[0], SIP_INC_RINGING)) {
+ (*inringing)--;
+ ast_clear_flag(&fup->flags[0], SIP_INC_RINGING);
+ }
+ } else {
+ *inringing = 0;
+ }
+ ao2_unlock(p);
+ sip_pvt_unlock(fup);
+ }
+
+ /* Decrement onhold count if applicable */
+ sip_pvt_lock(fup);
+ ao2_lock(p);
+ if (ast_test_flag(&fup->flags[1], SIP_PAGE2_CALL_ONHOLD) && sip_cfg.notifyhold) {
+ ast_clear_flag(&fup->flags[1], SIP_PAGE2_CALL_ONHOLD);
+ ao2_unlock(p);
+ sip_pvt_unlock(fup);
+ sip_peer_hold(fup, FALSE);
+ } else {
+ ao2_unlock(p);
+ sip_pvt_unlock(fup);
+ }
+ if (sipdebug)
+ ast_debug(2, "Call %s %s '%s' removed from call limit %d\n", outgoing ? "to" : "from", "peer", name, *call_limit);
+ break;
+
+ case INC_CALL_RINGING:
+ case INC_CALL_LIMIT:
+ /* If call limit is active and we have reached the limit, reject the call */
+ if (*call_limit > 0 ) {
+ if (*inuse >= *call_limit) {
+ ast_log(LOG_NOTICE, "Call %s %s '%s' rejected due to usage limit of %d\n", outgoing ? "to" : "from", "peer", name, *call_limit);
+ unref_peer(p, "update_call_counter: unref peer p, call limit exceeded");
+ return -1;
+ }
+ }
+ if (inringing && (event == INC_CALL_RINGING)) {
+ sip_pvt_lock(fup);
+ ao2_lock(p);
+ if (!ast_test_flag(&fup->flags[0], SIP_INC_RINGING)) {
+ (*inringing)++;
+ ast_set_flag(&fup->flags[0], SIP_INC_RINGING);
+ }
+ ao2_unlock(p);
+ sip_pvt_unlock(fup);
+ }
+ if (inuse) {
+ sip_pvt_lock(fup);
+ ao2_lock(p);
+ if (!ast_test_flag(&fup->flags[0], SIP_INC_COUNT)) {
+ (*inuse)++;
+ ast_set_flag(&fup->flags[0], SIP_INC_COUNT);
+ }
+ ao2_unlock(p);
+ sip_pvt_unlock(fup);
+ }
+ if (sipdebug) {
+ ast_debug(2, "Call %s %s '%s' is %d out of %d\n", outgoing ? "to" : "from", "peer", name, *inuse, *call_limit);
+ }
+ break;
+
+ case DEC_CALL_RINGING:
+ if (inringing) {
+ sip_pvt_lock(fup);
+ ao2_lock(p);
+ if (ast_test_flag(&fup->flags[0], SIP_INC_RINGING)) {
+ if (*inringing > 0) {
+ (*inringing)--;
+ }
+ ast_clear_flag(&fup->flags[0], SIP_INC_RINGING);
+ }
+ ao2_unlock(p);
+ sip_pvt_unlock(fup);
+ }
+ break;
+
+ default:
+ ast_log(LOG_ERROR, "update_call_counter(%s, %d) called with no event!\n", name, event);
+ }
+
+ if (p) {
+ ast_devstate_changed(AST_DEVICE_UNKNOWN, "SIP/%s", p->name);
+ unref_peer(p, "update_call_counter: unref_peer from call counter");
+ }
+ return 0;
+}
+
+
+static void sip_destroy_fn(void *p)
+{
+ sip_destroy(p);
+}
+
+/*! \brief Destroy SIP call structure.
+ * Make it return NULL so the caller can do things like
+ * foo = sip_destroy(foo);
+ * and reduce the chance of bugs due to dangling pointers.
+ */
+struct sip_pvt *sip_destroy(struct sip_pvt *p)
+{
+ ast_debug(3, "Destroying SIP dialog %s\n", p->callid);
+ __sip_destroy(p, TRUE, TRUE);
+ return NULL;
+}
+
+/*! \brief Convert SIP hangup causes to Asterisk hangup causes */
+int hangup_sip2cause(int cause)
+{
+ /* Possible values taken from causes.h */
+
+ switch(cause) {
+ case 401: /* Unauthorized */
+ return AST_CAUSE_CALL_REJECTED;
+ case 403: /* Not found */
+ return AST_CAUSE_CALL_REJECTED;
+ case 404: /* Not found */
return AST_CAUSE_UNALLOCATED;
case 405: /* Method not allowed */
return AST_CAUSE_INTERWORKING;
sip_pvt_lock(i);
return NULL;
}
+ ast_channel_lock(tmp);
sip_pvt_lock(i);
+ ast_channel_cc_params_init(tmp, i->cc_params);
+ ast_channel_unlock(tmp);
tmp->tech = ( ast_test_flag(&i->flags[0], SIP_DTMF) == SIP_DTMF_INFO || ast_test_flag(&i->flags[0], SIP_DTMF) == SIP_DTMF_SHORTINFO) ? &sip_tech_info : &sip_tech;
return buf;
}
+static char *generate_uri(struct sip_pvt *pvt, char *buf, size_t size)
+{
+ struct ast_str *uri = ast_str_alloca(size);
+ int ourport = ntohs(pvt->ourip.sin_port);
+ ast_str_set(&uri, 0, "%s", pvt->socket.type == SIP_TRANSPORT_TLS ? "sips:" : "sip:");
+ /* Here would be a great place to generate a UUID, but for now we'll
+ * use the handy random string generation function we already have
+ */
+ ast_str_append(&uri, 0, "%s", generate_random_string(buf, size));
+ ast_str_append(&uri, 0, "@%s", ast_inet_ntoa(pvt->ourip.sin_addr));
+ if (!sip_standard_port(pvt->socket.type, ourport)) {
+ ast_str_append(&uri, 0, ":%d", ourport);
+ }
+ ast_copy_string(buf, ast_str_buffer(uri), size);
+ return buf;
+}
+
/*! \brief Build SIP Call-ID value for a non-REGISTER transaction */
static void build_callid_pvt(struct sip_pvt *pvt)
{
return NULL;
}
+ if (!(p->cc_params = ast_cc_config_params_init())) {
+ ao2_t_ref(p, -1, "Yuck, couldn't allocate cc_params struct. Get rid o' p");
+ return NULL;
+ }
+
if (req) {
set_socket_transport(&p->socket, req->socket.type); /* Later in ast_sip_ouraddrfor we need this to choose the right ip and port for the specific transport */
} else {
ast_clear_flag(&p->flags[1], SIP_PAGE2_CONNECTLINEUPDATE_PEND);
add_rpid(&resp, p);
}
+ if (ast_test_flag(&p->flags[0], SIP_OFFER_CC)) {
+ add_cc_call_info_to_response(p, &resp);
+ }
add_header_contentLength(&resp, 0);
/* If we are cancelling an incoming invite for some reason, add information
return send_response(p, &resp, reliable, seqno);
}
+static int transmit_response_with_sip_etag(struct sip_pvt *p, const char *msg, const struct sip_request *req, struct sip_esc_entry *esc_entry, int need_new_etag)
+{
+ struct sip_request resp;
+
+ if (need_new_etag) {
+ create_new_sip_etag(esc_entry, 1);
+ }
+ respprep(&resp, p, msg, req);
+ add_header(&resp, "SIP-ETag", esc_entry->entity_tag);
+
+ return send_response(p, &resp, 0, 0);
+}
+
static int temp_pvt_init(void *data)
{
struct sip_pvt *p = data;
dst->data->used = src->data->used;
}
+static void add_cc_call_info_to_response(struct sip_pvt *p, struct sip_request *resp)
+{
+ char uri[SIPBUFSIZE];
+ struct ast_str *header = ast_str_alloca(SIPBUFSIZE);
+ struct ast_cc_agent *agent = find_sip_cc_agent_by_original_callid(p);
+ struct sip_cc_agent_pvt *agent_pvt;
+
+ if (!agent) {
+ /* Um, what? How could the SIP_OFFER_CC flag be set but there not be an
+ * agent? Oh well, we'll just warn and return without adding the header.
+ */
+ ast_log(LOG_WARNING, "Can't find SIP CC agent for call '%s' even though OFFER_CC flag was set?\n", p->callid);
+ return;
+ }
+
+ agent_pvt = agent->private_data;
+
+ if (!ast_strlen_zero(agent_pvt->subscribe_uri)) {
+ ast_copy_string(uri, agent_pvt->subscribe_uri, sizeof(uri));
+ } else {
+ generate_uri(p, uri, sizeof(uri));
+ ast_copy_string(agent_pvt->subscribe_uri, uri, sizeof(agent_pvt->subscribe_uri));
+ }
+ /* XXX Hardcode "NR" as the m reason for now. This should perhaps be changed
+ * to be more accurate. This parameter has no bearing on the actual operation
+ * of the feature; it's just there for informational purposes.
+ */
+ ast_str_set(&header, 0, "<%s>;purpose=call-completion;m=%s", uri, "NR");
+ add_header(resp, "Call-Info", ast_str_buffer(header));
+ ao2_ref(agent, -1);
+}
+
/*! \brief Used for 200 OK and 183 early media
\return Will return XMIT_ERROR for network errors.
*/
if (rpid == TRUE) {
add_rpid(&resp, p);
}
+ if (ast_test_flag(&p->flags[0], SIP_OFFER_CC)) {
+ add_cc_call_info_to_response(p, &resp);
+ }
if (p->rtp) {
if (!p->autoframing && !ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
ast_debug(1, "Setting framing from config on incoming call\n");
}
/*! \brief Initiate new SIP request to peer/user */
-static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod)
+static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod, const char * const explicit_uri)
{
struct ast_str *invite = ast_str_alloca(256);
char from[256];
else
snprintf(from, sizeof(from), "\"%s\" <sip:%s@%s>;tag=%s", n, l, d, p->tag);
- /* If we're calling a registered SIP peer, use the fullcontact to dial to the peer */
- if (!ast_strlen_zero(p->fullcontact)) {
- /* If we have full contact, trust it */
- ast_str_append(&invite, 0, "%s", p->fullcontact);
+
+ if (!ast_strlen_zero(explicit_uri)) {
+ ast_str_set(&invite, 0, "%s", explicit_uri);
} else {
- /* Otherwise, use the username while waiting for registration */
- ast_str_append(&invite, 0, "sip:");
- if (!ast_strlen_zero(p->username)) {
- n = p->username;
- if (sip_cfg.pedanticsipchecking) {
- ast_uri_encode(n, tmp_n, sizeof(tmp_n), 0);
- n = tmp_n;
+ /* If we're calling a registered SIP peer, use the fullcontact to dial to the peer */
+ if (!ast_strlen_zero(p->fullcontact)) {
+ /* If we have full contact, trust it */
+ ast_str_append(&invite, 0, "%s", p->fullcontact);
+ } else {
+ /* Otherwise, use the username while waiting for registration */
+ ast_str_append(&invite, 0, "sip:");
+ if (!ast_strlen_zero(p->username)) {
+ n = p->username;
+ if (sip_cfg.pedanticsipchecking) {
+ ast_uri_encode(n, tmp_n, sizeof(tmp_n), 0);
+ n = tmp_n;
+ }
+ ast_str_append(&invite, 0, "%s@", n);
}
- ast_str_append(&invite, 0, "%s@", n);
+ ast_str_append(&invite, 0, "%s", p->tohost);
+ if (p->portinuri)
+ ast_str_append(&invite, 0, ":%d", ntohs(p->sa.sin_port));
+ ast_str_append(&invite, 0, "%s", urioptions);
}
- ast_str_append(&invite, 0, "%s", p->tohost);
- if (p->portinuri)
- ast_str_append(&invite, 0, ":%d", ntohs(p->sa.sin_port));
- ast_str_append(&invite, 0, "%s", urioptions);
}
/* If custom URI options have been provided, append them */
return;
}
- /* We at least have a number to place in the Diversion header, which is enough */
- if (ast_strlen_zero(diverting_name)) {
- snprintf(header_text, sizeof(header_text), "<sip:%s@%s>;reason=%s", diverting_number, ast_inet_ntoa(pvt->ourip.sin_addr), reason);
- } else {
- snprintf(header_text, sizeof(header_text), "\"%s\" <sip:%s@%s>;reason=%s", diverting_name, diverting_number, ast_inet_ntoa(pvt->ourip.sin_addr), reason);
+ /* We at least have a number to place in the Diversion header, which is enough */
+ if (ast_strlen_zero(diverting_name)) {
+ snprintf(header_text, sizeof(header_text), "<sip:%s@%s>;reason=%s", diverting_number, ast_inet_ntoa(pvt->ourip.sin_addr), reason);
+ } else {
+ snprintf(header_text, sizeof(header_text), "\"%s\" <sip:%s@%s>;reason=%s", diverting_name, diverting_number, ast_inet_ntoa(pvt->ourip.sin_addr), reason);
+ }
+
+ add_header(req, "Diversion", header_text);
+}
+
+static int transmit_publish(struct sip_epa_entry *epa_entry, enum sip_publish_type publish_type, const char * const explicit_uri)
+{
+ struct sip_pvt *pvt;
+ int expires;
+
+ epa_entry->publish_type = publish_type;
+
+ if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_PUBLISH, NULL))) {
+ return -1;
+ }
+
+ sip_pvt_lock(pvt);
+
+ if (create_addr(pvt, epa_entry->destination, NULL, TRUE)) {
+ dialog_unlink_all(pvt, TRUE, TRUE);
+ dialog_unref(pvt, "create_addr failed in transmit_publish. Unref dialog");
}
+ ast_sip_ouraddrfor(&pvt->sa.sin_addr, &pvt->ourip, pvt);
+ ast_set_flag(&pvt->flags[0], SIP_OUTGOING);
+ expires = (publish_type == SIP_PUBLISH_REMOVE) ? 0 : DEFAULT_PUBLISH_EXPIRES;
+ pvt->expiry = expires;
- add_header(req, "Diversion", header_text);
+ /* Bump refcount for sip_pvt's reference */
+ ao2_ref(epa_entry, +1);
+ pvt->epa_entry = epa_entry;
+
+ transmit_invite(pvt, SIP_PUBLISH, FALSE, 2, explicit_uri);
+ sip_pvt_unlock(pvt);
+ sip_scheddestroy(pvt, DEFAULT_TRANS_TIMEOUT);
+ dialog_unref(pvt, "Done with the sip_pvt allocated for transmitting PUBLISH");
+ return 0;
}
/*! \brief Build REFER/INVITE/OPTIONS/SUBSCRIBE message and transmit it
\param sipmethod unknown
*/
-static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init)
+static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init, const char * const explicit_uri)
{
struct sip_request req;
struct ast_variable *var;
build_via(p);
}
if (init > 1)
- initreqprep(&req, p, sipmethod);
+ initreqprep(&req, p, sipmethod, explicit_uri);
else
/* If init=1, we should not generate a new branch. If it's 0, we need a new branch. */
reqprep(&req, p, sipmethod, 0, init ? 0 : 1);
add_header(&req, "Referred-By", buf);
}
}
- } else if (sipmethod == SIP_SUBSCRIBE) { /* We only support sending MWI subscriptions right now */
+ } else if (sipmethod == SIP_SUBSCRIBE) {
char buf[SIPBUFSIZE];
-
- add_header(&req, "Event", "message-summary");
- add_header(&req, "Accept", "application/simple-message-summary");
- snprintf(buf, sizeof(buf), "%d", mwi_expiry);
+ if (p->subscribed == MWI_NOTIFICATION) {
+ add_header(&req, "Event", "message-summary");
+ add_header(&req, "Accept", "application/simple-message-summary");
+ } else if (p->subscribed == CALL_COMPLETION) {
+ add_header(&req, "Event", "call-completion");
+ add_header(&req, "Accept", "application/call-completion");
+ }
+ snprintf(buf, sizeof(buf), "%d", p->expiry);
add_header(&req, "Expires", buf);
}
add_header_contentLength(&req, ast_str_strlen(p->notify->content));
if (ast_str_strlen(p->notify->content))
add_line(&req, ast_str_buffer(p->notify->content));
+ } else if (sipmethod == SIP_PUBLISH) {
+ char expires[SIPBUFSIZE];
+ switch (p->epa_entry->static_data->event) {
+ case CALL_COMPLETION:
+ snprintf(expires, sizeof(expires), "%d", p->expiry);
+ add_header(&req, "Event", "call-completion");
+ add_header(&req, "Expires", expires);
+ if (p->epa_entry->publish_type != SIP_PUBLISH_INITIAL) {
+ add_header(&req, "SIP-If-Match", p->epa_entry->entity_tag);
+ }
+ if (!ast_strlen_zero(p->epa_entry->body)) {
+ add_header(&req, "Content-Type", "application/pidf+xml");
+ add_header_contentLength(&req, strlen(p->epa_entry->body));
+ add_line(&req, p->epa_entry->body);
+ } else {
+ add_header_contentLength(&req, 0);
+ }
+ default:
+ break;
+ }
} else {
add_header_contentLength(&req, 0);
}
if (!p->initreq.headers || init > 2)
initialize_initreq(p, &req);
- if (sipmethod == SIP_INVITE) {
+ if (sipmethod == SIP_INVITE || sipmethod == SIP_SUBSCRIBE) {
p->lastinvite = p->ocseq;
}
return send_request(p, &req, init ? XMIT_CRITICAL : XMIT_RELIABLE, p->ocseq);
/* If we already have a subscription up simply send a resubscription */
if (mwi->call) {
- transmit_invite(mwi->call, SIP_SUBSCRIBE, 0, 0);
+ transmit_invite(mwi->call, SIP_SUBSCRIBE, 0, 0, NULL);
return 0;
}
mwi->call = dialog_unref(mwi->call, "unref dialog after unlink_all");
return 0;
}
+
+ mwi->call->expiry = mwi_expiry;
if (!mwi->dnsmgr && mwi->portno) {
mwi->call->sa.sin_port = htons(mwi->portno);
mwi->call->mwi = ASTOBJ_REF(mwi);
/* Actually send the packet */
- transmit_invite(mwi->call, SIP_SUBSCRIBE, 0, 2);
+ transmit_invite(mwi->call, SIP_SUBSCRIBE, 0, 2, NULL);
return 0;
}
}
}
+static int transmit_cc_notify(struct ast_cc_agent *agent, struct sip_pvt *subscription, enum sip_cc_notify_state state)
+{
+ struct sip_request req;
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ char uri[SIPBUFSIZE];
+ char state_str[64];
+
+ if (state < CC_QUEUED || state > CC_READY) {
+ ast_log(LOG_WARNING, "Invalid state provided for transmit_cc_notify (%d)\n", state);
+ return -1;
+ }
+
+ reqprep(&req, subscription, SIP_NOTIFY, 0, TRUE);
+ snprintf(state_str, sizeof(state_str), "%s\r\n", sip_cc_notify_state_map[state].state_string);
+ add_header(&req, "Event", "call-completion");
+ add_header(&req, "Content-Type", "application/call-completion");
+ if (state == CC_READY) {
+ generate_uri(subscription, agent_pvt->notify_uri, sizeof(agent_pvt->notify_uri));
+ snprintf(uri, sizeof(uri) - 1, "cc-URI: %s\r\n", agent_pvt->notify_uri);
+ }
+ add_header_contentLength(&req, strlen(state_str) +
+ (state == CC_READY ? strlen(uri) : 0));
+ add_line(&req, state_str);
+ if (state == CC_READY) {
+ add_line(&req, uri);
+ }
+ return send_request(subscription, &req, XMIT_RELIABLE, subscription->ocseq);
+}
/*! \brief Used in the SUBSCRIBE notification subsystem (RFC3265) */
static int transmit_state_notify(struct sip_pvt *p, int state, int full, int timeout)
int ourport = ntohs(p->ourip.sin_port);
const char *exten = S_OR(vmexten, default_vmexten);
- initreqprep(&req, p, SIP_NOTIFY);
+ initreqprep(&req, p, SIP_NOTIFY, NULL);
add_header(&req, "Event", "message-summary");
add_header(&req, "Content-Type", default_notifymime);
ast_str_append(&out, 0, "Messages-Waiting: %s\r\n", newmsgs ? "yes" : "no");
dialog_ref(p, "bump the count of p, which transmit_sip_request will decrement.");
sip_scheddestroy(p, SIP_TRANS_TIMEOUT);
- transmit_invite(p, SIP_NOTIFY, 0, 2);
+ transmit_invite(p, SIP_NOTIFY, 0, 2, NULL);
astman_send_ack(s, m, "Notify Sent");
ast_variables_destroy(vars);
the dialplan, so that the outbound call also is a sips: call or encrypted
IAX2 call. If that's not available, the call should FAIL.
*/
-static int get_destination(struct sip_pvt *p, struct sip_request *oreq)
+static int get_destination(struct sip_pvt *p, struct sip_request *oreq, int *cc_recall_core_id)
{
char tmp[256] = "", *uri, *domain, *dummy = NULL;
char tmpf[256] = "", *from = NULL;
struct sip_request *req;
char *decoded_uri;
-
+
req = oreq;
if (!req)
req = &p->initreq;
if (req->rlPart2)
ast_copy_string(tmp, REQ_OFFSET_TO_STR(req, rlPart2), sizeof(tmp));
- uri = get_in_brackets(tmp);
+ uri = ast_strdupa(get_in_brackets(tmp));
if (parse_uri(uri, "sip:,sips:", &uri, &dummy, &domain, &dummy, NULL)) {
ast_log(LOG_WARNING, "Not a SIP header (%s)?\n", uri);
char hint[AST_MAX_EXTENSION];
return (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, p->context, p->exten) ? 0 : -1);
} else {
+ struct ast_cc_agent *agent;
decoded_uri = ast_strdupa(uri);
ast_uri_decode(decoded_uri);
/* Check the dialplan for the username part of the request URI,
if (!oreq)
ast_string_field_set(p, exten, decoded_uri);
return 0;
+ } else if ((agent = find_sip_cc_agent_by_notify_uri(tmp))) {
+ struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
+ /* This is a CC recall. We can set p's extension to the exten from
+ * the original INVITE
+ */
+ ast_string_field_set(p, exten, agent_pvt->original_exten);
+ /* And we need to let the CC core know that the caller is attempting
+ * his recall
+ */
+ ast_cc_agent_recalling(agent->core_id, "SIP caller %s is attempting recall",
+ agent->device_name);
+ if (cc_recall_core_id) {
+ *cc_recall_core_id = agent->core_id;
+ }
+ ao2_ref(agent, -1);
+ return 0;
}
}
ast_string_field_set(p, engine, peer->engine);
p->disallowed_methods = peer->disallowed_methods;
set_pvt_allowed_methods(p, req);
+ ast_cc_copy_config_params(p->cc_params, peer->cc_params);
if (peer->callingpres) /* Peer calling pres setting will override RPID */
p->callingpres = peer->callingpres;
if (peer->maxms && peer->lastms)
ast_cli(a->fd, "Sending NOTIFY of type '%s' to '%s'\n", a->argv[2], a->argv[i]);
dialog_ref(p, "bump the count of p, which transmit_sip_request will decrement.");
sip_scheddestroy(p, SIP_TRANS_TIMEOUT);
- transmit_invite(p, SIP_NOTIFY, 0, 2);
+ transmit_invite(p, SIP_NOTIFY, 0, 2, NULL);
}
return CLI_SUCCESS;
/* Now we have a reply digest */
p->options->auth = digest;
p->options->authheader = respheader;
- return transmit_invite(p, sipmethod, sipmethod == SIP_INVITE, init);
+ return transmit_invite(p, sipmethod, sipmethod == SIP_INVITE, init, NULL);
}
/*! \brief reply to authentication for outbound registrations
}
}
+static void cc_handle_publish_error(struct sip_pvt *pvt, const int resp, struct sip_request *req, struct sip_epa_entry *epa_entry)
+{
+ struct cc_epa_entry *cc_entry = epa_entry->instance_data;
+ struct sip_monitor_instance *monitor_instance = ao2_callback(sip_monitor_instances, 0,
+ find_sip_monitor_instance_by_suspension_entry, epa_entry);
+ const char *min_expires;
+
+ if (!monitor_instance) {
+ ast_log(LOG_WARNING, "Can't find monitor_instance corresponding to epa_entry %p.\n", epa_entry);
+ return;
+ }
+
+ if (resp != 423) {
+ ast_cc_monitor_failed(cc_entry->core_id, monitor_instance->device_name,
+ "Received error response to our PUBLISH");
+ ao2_ref(monitor_instance, -1);
+ return;
+ }
+
+ /* Allrighty, the other end doesn't like our Expires value. They think it's
+ * too small, so let's see if they've provided a more sensible value. If they
+ * haven't, then we'll just double our Expires value and see if they like that
+ * instead.
+ *
+ * XXX Ideally this logic could be placed into its own function so that SUBSCRIBE,
+ * PUBLISH, and REGISTER could all benefit from the same shared code.
+ */
+ min_expires = get_header(req, "Min-Expires");
+ if (ast_strlen_zero(min_expires)) {
+ pvt->expiry *= 2;
+ if (pvt->expiry < 0) {
+ /* You dork! You overflowed! */
+ ast_cc_monitor_failed(cc_entry->core_id, monitor_instance->device_name,
+ "PUBLISH expiry overflowed");
+ ao2_ref(monitor_instance, -1);
+ return;
+ }
+ } else if (sscanf(min_expires, "%d", &pvt->expiry) != 1) {
+ ast_cc_monitor_failed(cc_entry->core_id, monitor_instance->device_name,
+ "Min-Expires has non-numeric value");
+ ao2_ref(monitor_instance, -1);
+ return;
+ }
+ /* At this point, we have most certainly changed pvt->expiry, so try transmitting
+ * again
+ */
+ transmit_invite(pvt, SIP_PUBLISH, FALSE, 0, NULL);
+ ao2_ref(monitor_instance, -1);
+}
+
+static void handle_response_publish(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno)
+{
+ struct sip_epa_entry *epa_entry = p->epa_entry;
+ const char *etag = get_header(req, "Sip-ETag");
+
+ ast_assert(epa_entry != NULL);
+
+ if (resp == 401 || resp == 407) {
+ ast_string_field_set(p, theirtag, NULL);
+ if (p->options) {
+ p->options->auth_type = (resp == 401 ? WWW_AUTH : PROXY_AUTH);
+ }
+ if ((p->authtries == MAX_AUTHTRIES) || do_proxy_auth(p, req, resp, SIP_PUBLISH, 0)) {
+ ast_log(LOG_NOTICE, "Failed to authenticate on PUBLISH to '%s'\n", get_header(&p->initreq, "From"));
+ pvt_set_needdestroy(p, "Failed to authenticate on PUBLISH");
+ sip_alreadygone(p);
+ }
+ return;
+ }
+
+ if (resp == 501 || resp == 405) {
+ mark_method_unallowed(&p->allowed_methods, SIP_PUBLISH);
+ }
+
+ if (resp == 200) {
+ p->authtries = 0;
+ /* If I've read section 6, item 6 of RFC 3903 correctly,
+ * an ESC will only generate a new etag when it sends a 200 OK
+ */
+ if (!ast_strlen_zero(etag)) {
+ ast_copy_string(epa_entry->entity_tag, etag, sizeof(epa_entry->entity_tag));
+ }
+ /* The nominal case. Everything went well. Everybody is happy.
+ * Each EPA will have a specific action to take as a result of this
+ * development, so ... callbacks!
+ */
+ if (epa_entry->static_data->handle_ok) {
+ epa_entry->static_data->handle_ok(p, req, epa_entry);
+ }
+ } else {
+ /* Rather than try to make individual callbacks for each error
+ * type, there is just a single error callback. The callback
+ * can distinguish between error messages and do what it needs to
+ */
+ if (epa_entry->static_data->handle_error) {
+ epa_entry->static_data->handle_error(p, resp, req, epa_entry);
+ }
+ }
+}
+
/*! \brief Handle SIP response to INVITE dialogue */
static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, int seqno)
{
connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER;
ast_channel_queue_connected_line_update(p->owner, &connected);
}
+ sip_handle_cc(p, req, AST_CC_CCNR);
ast_queue_control(p->owner, AST_CONTROL_RINGING);
if (p->owner->_state != AST_STATE_UP) {
ast_setstate(p->owner, AST_STATE_RINGING);
struct ast_party_redirecting redirecting = {{0,},};
change_redirecting_information(p, req, &redirecting, FALSE);
ast_channel_queue_redirecting_update(p->owner, &redirecting);
+ sip_handle_cc(p, req, AST_CC_CCNR);
}
check_pendings(p);
break;
connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER;
ast_channel_queue_connected_line_update(p->owner, &connected);
}
+ sip_handle_cc(p, req, AST_CC_CCNR);
}
if (find_sdp(req)) {
if (p->invitestate != INV_CANCELLED)
need to hang around for something more "definitive" */
if (resp != 100)
handle_response_peerpoke(p, resp, req);
+ } else if (sipmethod == SIP_PUBLISH) {
+ /* SIP PUBLISH transcends this morass of doodoo and instead
+ * we just always call the response handler. Good gravy!
+ */
+ handle_response_publish(p, resp, rest, req, seqno);
} else if (ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
switch(resp) {
case 100: /* 100 Trying */
case 486: /* Busy here */
case 600: /* Busy everywhere */
case 603: /* Decline */
- if (p->owner)
+ if (p->owner) {
+ sip_handle_cc(p, req, AST_CC_CCBS);
ast_queue_control(p->owner, AST_CONTROL_BUSY);
+ }
break;
case 482: /*!
\note SIP is incapable of performing a hairpin call, which
return NULL;
}
+static int handle_cc_notify(struct sip_pvt *pvt, struct sip_request *req)
+{
+ struct sip_monitor_instance *monitor_instance = ao2_callback(sip_monitor_instances, 0,
+ find_sip_monitor_instance_by_subscription_pvt, pvt);
+ const char *status = get_body(req, "cc-state", ':');
+ struct cc_epa_entry *cc_entry;
+ char *uri;
+
+ if (!monitor_instance) {
+ transmit_response(pvt, "400 Bad Request", req);
+ return -1;
+ }
+
+ if (ast_strlen_zero(status)) {
+ ao2_ref(monitor_instance, -1);
+ transmit_response(pvt, "400 Bad Request", req);
+ return -1;
+ }
+
+ if (!strcmp(status, "queued")) {
+ /* We've been told that we're queued. This is the endpoint's way of telling
+ * us that it has accepted our CC request. We need to alert the core of this
+ * development
+ */
+ ast_cc_monitor_request_acked(monitor_instance->core_id, "SIP endpoint %s accepted request", monitor_instance->device_name);
+ transmit_response(pvt, "200 OK", req);
+ ao2_ref(monitor_instance, -1);
+ return 0;
+ }
+
+ /* It's open! Yay! */
+ uri = get_body(req, "cc-URI", ':');
+ if (ast_strlen_zero(uri)) {
+ uri = get_in_brackets((char *)get_header(req, "From"));
+ }
+
+ ast_string_field_set(monitor_instance, notify_uri, uri);
+ if (monitor_instance->suspension_entry) {
+ cc_entry = monitor_instance->suspension_entry->instance_data;
+ if (cc_entry->current_state == CC_CLOSED) {
+ /* If we've created a suspension entry and the current state is closed, then that means
+ * we got a notice from the CC core earlier to suspend monitoring, but because this particular
+ * call leg had not yet notified us that it was ready for recall, it meant that we
+ * could not yet send a PUBLISH. Now, however, we can.
+ */
+ construct_pidf_body(CC_CLOSED, monitor_instance->suspension_entry->body,
+ sizeof(monitor_instance->suspension_entry->body), monitor_instance->peername);
+ transmit_publish(monitor_instance->suspension_entry, SIP_PUBLISH_INITIAL, monitor_instance->notify_uri);
+ } else {
+ ast_cc_monitor_callee_available(monitor_instance->core_id, "SIP monitored callee has become available");
+ }
+ } else {
+ ast_cc_monitor_callee_available(monitor_instance->core_id, "SIP monitored callee has become available");
+ }
+ ao2_ref(monitor_instance, -1);
+ transmit_response(pvt, "200 OK", req);
+
+ return 0;
+}
+
/*! \brief Handle incoming notifications */
static int handle_request_notify(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, int seqno, const char *e)
{
/* Used by Sipura/Linksys for NAT pinhole,
* just confirm that we recieved the packet. */
transmit_response(p, "200 OK", req);
+ } else if (!strcmp(event, "call-completion")) {
+ res = handle_cc_notify(p, req);
} else {
/* We don't understand this event. */
transmit_response(p, "489 Bad event", req);
return 0;
}
- res = get_destination(p, req);
+ res = get_destination(p, req, NULL);
build_contact(p);
if (ast_strlen_zero(p->context))
/* This is a new invite */
/* Handle authentication if this is our first invite */
struct ast_party_redirecting redirecting = {{0,},};
+ int cc_recall_core_id = -1;
set_pvt_allowed_methods(p, req);
res = check_user(p, req, SIP_INVITE, e, XMIT_RELIABLE, sin);
if (res == AUTH_CHALLENGE_SENT) {
res = 0;
goto request_invite_cleanup;
}
- gotdest = get_destination(p, NULL); /* Get destination right away */
+ gotdest = get_destination(p, NULL, &cc_recall_core_id); /* Get destination right away */
change_redirecting_information(p, req, &redirecting, FALSE); /*Will return immediately if no Diversion header is present */
extract_uri(p, req); /* Get the Contact URI */
build_contact(p); /* Build our contact header */
make_our_tag(p->tag, sizeof(p->tag));
/* First invitation - create the channel */
c = sip_new(p, AST_STATE_DOWN, S_OR(p->peername, NULL), NULL);
+ if (cc_recall_core_id != -1) {
+ ast_setup_cc_recall_datastore(c, cc_recall_core_id);
+ ast_cc_agent_set_interfaces_chanvar(c);
+ }
*recount = 1;
/* Save Record-Route for any later requests we make on this dialogue */
}
}
- dlg_min_se = st_get_se(p, FALSE);
- switch (st_get_mode(p)) {
- case SESSION_TIMER_MODE_ACCEPT:
- case SESSION_TIMER_MODE_ORIGINATE:
- if (uac_max_se > 0 && uac_max_se < dlg_min_se) {
- transmit_response_with_minse(p, "422 Session Interval Too Small", req, dlg_min_se);
- p->invitestate = INV_COMPLETED;
- if (!p->lastinvite) {
- sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ dlg_min_se = st_get_se(p, FALSE);
+ switch (st_get_mode(p)) {
+ case SESSION_TIMER_MODE_ACCEPT:
+ case SESSION_TIMER_MODE_ORIGINATE:
+ if (uac_max_se > 0 && uac_max_se < dlg_min_se) {
+ transmit_response_with_minse(p, "422 Session Interval Too Small", req, dlg_min_se);
+ p->invitestate = INV_COMPLETED;
+ if (!p->lastinvite) {
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ }
+ res = -1;
+ goto request_invite_cleanup;
+ }
+
+ p->stimer->st_active_peer_ua = TRUE;
+ st_active = TRUE;
+ if (st_ref == SESSION_TIMER_REFRESHER_AUTO) {
+ st_ref = st_get_refresher(p);
+ }
+
+ if (uac_max_se > 0) {
+ int dlg_max_se = st_get_se(p, TRUE);
+ if (dlg_max_se >= uac_min_se) {
+ st_interval = (uac_max_se < dlg_max_se) ? uac_max_se : dlg_max_se;
+ } else {
+ st_interval = uac_max_se;
+ }
+ } else {
+ /* Set to default max value */
+ st_interval = global_max_se;
+ }
+ break;
+
+ case SESSION_TIMER_MODE_REFUSE:
+ if (p->reqsipoptions & SIP_OPT_TIMER) {
+ transmit_response_with_unsupported(p, "420 Option Disabled", req, required);
+ ast_log(LOG_WARNING, "Received SIP INVITE with supported but disabled option: %s\n", required);
+ p->invitestate = INV_COMPLETED;
+ if (!p->lastinvite) {
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ }
+ res = -1;
+ goto request_invite_cleanup;
+ }
+ break;
+
+ default:
+ ast_log(LOG_ERROR, "Internal Error %d at %s:%d\n", st_get_mode(p), __FILE__, __LINE__);
+ break;
+ }
+ } else {
+ /* The UAC did not request session-timers. Asterisk (UAS), will now decide
+ (based on session-timer-mode in sip.conf) whether to run session-timers for
+ this session or not. */
+ switch (st_get_mode(p)) {
+ case SESSION_TIMER_MODE_ORIGINATE:
+ st_active = TRUE;
+ st_interval = st_get_se(p, TRUE);
+ st_ref = SESSION_TIMER_REFRESHER_UAS;
+ p->stimer->st_active_peer_ua = FALSE;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (reinvite == 0) {
+ /* Session-Timers: Start session refresh timer based on negotiation/config */
+ if (st_active == TRUE) {
+ p->stimer->st_active = TRUE;
+ p->stimer->st_interval = st_interval;
+ p->stimer->st_ref = st_ref;
+ start_session_timer(p);
+ }
+ } else {
+ if (p->stimer->st_active == TRUE) {
+ /* Session-Timers: A re-invite request sent within a dialog will serve as
+ a refresh request, no matter whether the re-invite was sent for refreshing
+ the session or modifying it.*/
+ ast_debug (2, "Restarting session-timers on a refresh - %s\n", p->callid);
+
+ /* The UAC may be adjusting the session-timers mid-session */
+ if (st_interval > 0) {
+ p->stimer->st_interval = st_interval;
+ p->stimer->st_ref = st_ref;
+ }
+
+ restart_session_timer(p);
+ if (p->stimer->st_expirys > 0) {
+ p->stimer->st_expirys--;
+ }
+ }
+ }
+
+ if (!req->ignore && p)
+ p->lastinvite = seqno;
+
+ if (replace_id) { /* Attended transfer or call pickup - we're the target */
+ if (!ast_strlen_zero(pickup.exten)) {
+ append_history(p, "Xfer", "INVITE/Replace received");
+
+ /* Let the caller know we're giving it a shot */
+ transmit_response(p, "100 Trying", req);
+ ast_setstate(c, AST_STATE_RING);
+
+ /* Do the pickup itself */
+ ast_channel_unlock(c);
+ *nounlock = 1;
+
+ /* since p->owner (c) is unlocked, we need to go ahead and unlock pvt for both
+ * magic pickup and ast_hangup. Both of these functions will attempt to lock
+ * p->owner again, which can cause a deadlock if we already hold a lock on p.
+ * Locking order is, channel then pvt. Dead lock avoidance must be used if
+ * called the other way around. */
+ sip_pvt_unlock(p);
+ do_magic_pickup(c, pickup.exten, pickup.context);
+ /* Now we're either masqueraded or we failed to pickup, in either case we... */
+ ast_hangup(c);
+ sip_pvt_lock(p); /* pvt is expected to remain locked on return, so re-lock it */
+
+ res = 0;
+ goto request_invite_cleanup;
+ } else {
+ /* Go and take over the target call */
+ if (sipdebug)
+ ast_debug(4, "Sending this call to the invite/replcaes handler %s\n", p->callid);
+ res = handle_invite_replaces(p, req, debug, seqno, sin, nounlock);
+ refer_locked = 0;
+ goto request_invite_cleanup;
+ }
+ }
+
+
+ if (c) { /* We have a call -either a new call or an old one (RE-INVITE) */
+ enum ast_channel_state c_state = c->_state;
+
+ if (c_state != AST_STATE_UP && reinvite &&
+ (p->invitestate == INV_TERMINATED || p->invitestate == INV_CONFIRMED)) {
+ /* If these conditions are true, and the channel is still in the 'ringing'
+ * state, then this likely means that we have a situation where the initial
+ * INVITE transaction has completed *but* the channel's state has not yet been
+ * changed to UP. The reason this could happen is if the reinvite is received
+ * on the SIP socket prior to an application calling ast_read on this channel
+ * to read the answer frame we earlier queued on it. In this case, the reinvite
+ * is completely legitimate so we need to handle this the same as if the channel
+ * were already UP. Thus we are purposely falling through to the AST_STATE_UP case.
+ */
+ c_state = AST_STATE_UP;
+ }
+
+ switch(c_state) {
+ case AST_STATE_DOWN:
+ ast_debug(2, "%s: New call is still down.... Trying... \n", c->name);
+ transmit_provisional_response(p, "100 Trying", req, 0);
+ p->invitestate = INV_PROCEEDING;
+ ast_setstate(c, AST_STATE_RING);
+ if (strcmp(p->exten, ast_pickup_ext())) { /* Call to extension -start pbx on this call */
+ enum ast_pbx_result result;
+
+ result = ast_pbx_start(c);
+
+ switch(result) {
+ case AST_PBX_FAILED:
+ ast_log(LOG_WARNING, "Failed to start PBX :(\n");
+ p->invitestate = INV_COMPLETED;
+ transmit_response_reliable(p, "503 Unavailable", req);
+ break;
+ case AST_PBX_CALL_LIMIT:
+ ast_log(LOG_WARNING, "Failed to start PBX (call limit reached) \n");
+ p->invitestate = INV_COMPLETED;
+ transmit_response_reliable(p, "480 Temporarily Unavailable", req);
+ break;
+ case AST_PBX_SUCCESS:
+ /* nothing to do */
+ break;
}
- res = -1;
- goto request_invite_cleanup;
- }
- p->stimer->st_active_peer_ua = TRUE;
- st_active = TRUE;
- if (st_ref == SESSION_TIMER_REFRESHER_AUTO) {
- st_ref = st_get_refresher(p);
- }
+ if (result) {
- if (uac_max_se > 0) {
- int dlg_max_se = st_get_se(p, TRUE);
- if (dlg_max_se >= uac_min_se) {
- st_interval = (uac_max_se < dlg_max_se) ? uac_max_se : dlg_max_se;
+ /* Unlock locks so ast_hangup can do its magic */
+ ast_channel_unlock(c);
+ sip_pvt_unlock(p);
+ ast_hangup(c);
+ sip_pvt_lock(p);
+ c = NULL;
+ }
+ } else { /* Pickup call in call group */
+ ast_channel_unlock(c);
+ *nounlock = 1;
+ if (ast_pickup_call(c)) {
+ ast_log(LOG_NOTICE, "Nothing to pick up for %s\n", p->callid);
+ transmit_response_reliable(p, "503 Unavailable", req);
+ sip_alreadygone(p);
+ /* Unlock locks so ast_hangup can do its magic */
+ sip_pvt_unlock(p);
+ c->hangupcause = AST_CAUSE_CALL_REJECTED;
} else {
- st_interval = uac_max_se;
+ sip_pvt_unlock(p);
+ c->hangupcause = AST_CAUSE_NORMAL_CLEARING;
}
- } else {
- /* Set to default max value */
- st_interval = global_max_se;
+ p->invitestate = INV_COMPLETED;
+ ast_hangup(c);
+ sip_pvt_lock(p);
+ c = NULL;
}
break;
+ case AST_STATE_RING:
+ transmit_provisional_response(p, "100 Trying", req, 0);
+ p->invitestate = INV_PROCEEDING;
+ break;
+ case AST_STATE_RINGING:
+ transmit_provisional_response(p, "180 Ringing", req, 0);
+ p->invitestate = INV_PROCEEDING;
+ break;
+ case AST_STATE_UP:
+ ast_debug(2, "%s: This call is UP.... \n", c->name);
- case SESSION_TIMER_MODE_REFUSE:
- if (p->reqsipoptions & SIP_OPT_TIMER) {
- transmit_response_with_unsupported(p, "420 Option Disabled", req, required);
- ast_log(LOG_WARNING, "Received SIP INVITE with supported but disabled option: %s\n", required);
- p->invitestate = INV_COMPLETED;
- if (!p->lastinvite) {
- sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
- }
- res = -1;
- goto request_invite_cleanup;
+ transmit_response(p, "100 Trying", req);
+
+ if (p->t38.state == T38_PEER_REINVITE) {
+ p->t38id = ast_sched_add(sched, 5000, sip_t38_abort, dialog_ref(p, "passing dialog ptr into sched structure based on t38id for sip_t38_abort."));
+ } else if (p->t38.state == T38_ENABLED) {
+ ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
+ transmit_response_with_t38_sdp(p, "200 OK", req, (reinvite ? XMIT_RELIABLE : (req->ignore ? XMIT_UNRELIABLE : XMIT_CRITICAL)));
+ } else if (p->t38.state == T38_DISABLED) {
+ /* If this is not a re-invite or something to ignore - it's critical */
+ ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
+ transmit_response_with_sdp(p, "200 OK", req, (reinvite ? XMIT_RELIABLE : (req->ignore ? XMIT_UNRELIABLE : XMIT_CRITICAL)), p->session_modify == TRUE ? FALSE : TRUE, FALSE);
}
- break;
+ p->invitestate = INV_TERMINATED;
+ break;
default:
- ast_log(LOG_ERROR, "Internal Error %d at %s:%d\n", st_get_mode(p), __FILE__, __LINE__);
+ ast_log(LOG_WARNING, "Don't know how to handle INVITE in state %d\n", c->_state);
+ transmit_response(p, "100 Trying", req);
break;
}
} else {
- /* The UAC did not request session-timers. Asterisk (UAS), will now decide
- (based on session-timer-mode in sip.conf) whether to run session-timers for
- this session or not. */
- switch (st_get_mode(p)) {
- case SESSION_TIMER_MODE_ORIGINATE:
- st_active = TRUE;
- st_interval = st_get_se(p, TRUE);
- st_ref = SESSION_TIMER_REFRESHER_UAS;
- p->stimer->st_active_peer_ua = FALSE;
- break;
+ if (p && (p->autokillid == -1)) {
+ const char *msg;
- default:
- break;
+ if (!p->jointcapability)
+ msg = "488 Not Acceptable Here (codec error)";
+ else {
+ ast_log(LOG_NOTICE, "Unable to create/find SIP channel for this INVITE\n");
+ msg = "503 Unavailable";
+ }
+ transmit_response_reliable(p, msg, req);
+ p->invitestate = INV_COMPLETED;
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
}
}
- if (reinvite == 0) {
- /* Session-Timers: Start session refresh timer based on negotiation/config */
- if (st_active == TRUE) {
- p->stimer->st_active = TRUE;
- p->stimer->st_interval = st_interval;
- p->stimer->st_ref = st_ref;
- start_session_timer(p);
+request_invite_cleanup:
+
+ if (refer_locked && p->refer && p->refer->refer_call) {
+ sip_pvt_unlock(p->refer->refer_call);
+ if (p->refer->refer_call->owner) {
+ ast_channel_unlock(p->refer->refer_call->owner);
+ }
+ }
+
+ return res;
+}
+
+/*! \brief Find all call legs and bridge transferee with target
+ * called from handle_request_refer
+ *
+ * \note this function assumes two locks to begin with, sip_pvt transferer and current.chan1 (the pvt's owner)...
+ * 2 additional locks are held at the beginning of the function, targetcall_pvt, and targetcall_pvt's owner
+ * channel (which is stored in target.chan1). These 2 locks _MUST_ be let go by the end of the function. Do
+ * not be confused into thinking a pvt's owner is the same thing as the channels locked at the beginning of
+ * this function, after the masquerade this may not be true. Be consistent and unlock only the exact same
+ * pointers that were locked to begin with.
+ *
+ * If this function is successful, only the transferer pvt lock will remain on return. Setting nounlock indicates
+ * to handle_request_do() that the pvt's owner it locked does not require an unlock.
+ */
+static int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual *current, struct sip_request *req, int seqno, int *nounlock)
+{
+ struct sip_dual target; /* Chan 1: Call from tranferer to Asterisk */
+ /* Chan 2: Call from Asterisk to target */
+ int res = 0;
+ struct sip_pvt *targetcall_pvt;
+ struct ast_party_connected_line connected_to_transferee;
+ struct ast_party_connected_line connected_to_target;
+ char transferer_linkedid[32];
+ struct ast_channel *chans[2];
+
+ /* Check if the call ID of the replaces header does exist locally */
+ if (!(targetcall_pvt = get_sip_pvt_byid_locked(transferer->refer->replaces_callid, transferer->refer->replaces_callid_totag,
+ transferer->refer->replaces_callid_fromtag))) {
+ if (transferer->refer->localtransfer) {
+ /* We did not find the refered call. Sorry, can't accept then */
+ transmit_response(transferer, "202 Accepted", req);
+ /* Let's fake a response from someone else in order
+ to follow the standard */
+ transmit_notify_with_sipfrag(transferer, seqno, "481 Call leg/transaction does not exist", TRUE);
+ append_history(transferer, "Xfer", "Refer failed");
+ ast_clear_flag(&transferer->flags[0], SIP_GOTREFER);
+ transferer->refer->status = REFER_FAILED;
+ return -1;
}
+ /* Fall through for remote transfers that we did not find locally */
+ ast_debug(3, "SIP attended transfer: Not our call - generating INVITE with replaces\n");
+ return 0;
+ }
+
+ /* Ok, we can accept this transfer */
+ transmit_response(transferer, "202 Accepted", req);
+ append_history(transferer, "Xfer", "Refer accepted");
+ if (!targetcall_pvt->owner) { /* No active channel */
+ ast_debug(4, "SIP attended transfer: Error: No owner of target call\n");
+ /* Cancel transfer */
+ transmit_notify_with_sipfrag(transferer, seqno, "503 Service Unavailable", TRUE);
+ append_history(transferer, "Xfer", "Refer failed");
+ ast_clear_flag(&transferer->flags[0], SIP_GOTREFER);
+ transferer->refer->status = REFER_FAILED;
+ sip_pvt_unlock(targetcall_pvt);
+ if (targetcall_pvt)
+ ao2_t_ref(targetcall_pvt, -1, "Drop targetcall_pvt pointer");
+ return -1;
+ }
+
+ /* We have a channel, find the bridge */
+ target.chan1 = targetcall_pvt->owner; /* Transferer to Asterisk */
+ target.chan2 = ast_bridged_channel(targetcall_pvt->owner); /* Asterisk to target */
+
+ if (!target.chan2 || !(target.chan2->_state == AST_STATE_UP || target.chan2->_state == AST_STATE_RINGING) ) {
+ /* Wrong state of new channel */
+ if (target.chan2)
+ ast_debug(4, "SIP attended transfer: Error: Wrong state of target call: %s\n", ast_state2str(target.chan2->_state));
+ else if (target.chan1->_state != AST_STATE_RING)
+ ast_debug(4, "SIP attended transfer: Error: No target channel\n");
+ else
+ ast_debug(4, "SIP attended transfer: Attempting transfer in ringing state\n");
+ }
+
+ /* Transfer */
+ if (sipdebug) {
+ if (current->chan2) /* We have two bridges */
+ ast_debug(4, "SIP attended transfer: trying to bridge %s and %s\n", target.chan1->name, current->chan2->name);
+ else /* One bridge, propably transfer of IVR/voicemail etc */
+ ast_debug(4, "SIP attended transfer: trying to make %s take over (masq) %s\n", target.chan1->name, current->chan1->name);
+ }
+
+ ast_set_flag(&transferer->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Delay hangup */
+
+ ast_copy_string(transferer_linkedid, transferer->owner->linkedid, sizeof(transferer_linkedid));
+
+ /* Perform the transfer */
+ chans[0] = transferer->owner;
+ chans[1] = target.chan1;
+ ast_manager_event_multichan(EVENT_FLAG_CALL, "Transfer", 2, chans, "TransferMethod: SIP\r\nTransferType: Attended\r\nChannel: %s\r\nUniqueid: %s\r\nSIP-Callid: %s\r\nTargetChannel: %s\r\nTargetUniqueid: %s\r\n",
+ transferer->owner->name,
+ transferer->owner->uniqueid,
+ transferer->callid,
+ target.chan1->name,
+ target.chan1->uniqueid);
+ ast_party_connected_line_init(&connected_to_transferee);
+ ast_party_connected_line_init(&connected_to_target);
+ /* No need to lock current->chan1 here since it was locked in sipsock_read */
+ ast_party_connected_line_copy(&connected_to_transferee, ¤t->chan1->connected);
+ /* No need to lock target.chan1 here since it was locked in get_sip_pvt_byid_locked */
+ ast_party_connected_line_copy(&connected_to_target, &target.chan1->connected);
+ connected_to_target.source = connected_to_transferee.source = AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER;
+ res = attempt_transfer(current, &target);
+ if (res) {
+ /* Failed transfer */
+ transmit_notify_with_sipfrag(transferer, seqno, "486 Busy Here", TRUE);
+ append_history(transferer, "Xfer", "Refer failed");
+ ast_clear_flag(&transferer->flags[0], SIP_DEFER_BYE_ON_TRANSFER);
+ /* if transfer failed, go ahead and unlock targetcall_pvt and it's owner channel */
+ sip_pvt_unlock(targetcall_pvt);
+ ast_channel_unlock(target.chan1);
} else {
- if (p->stimer->st_active == TRUE) {
- /* Session-Timers: A re-invite request sent within a dialog will serve as
- a refresh request, no matter whether the re-invite was sent for refreshing
- the session or modifying it.*/
- ast_debug (2, "Restarting session-timers on a refresh - %s\n", p->callid);
+ /* Transfer succeeded! */
+ const char *xfersound = pbx_builtin_getvar_helper(target.chan1, "ATTENDED_TRANSFER_COMPLETE_SOUND");
- /* The UAC may be adjusting the session-timers mid-session */
- if (st_interval > 0) {
- p->stimer->st_interval = st_interval;
- p->stimer->st_ref = st_ref;
- }
+ /* target.chan1 was locked in get_sip_pvt_byid_locked, do not unlock target.chan1 before this */
+ ast_cel_report_event(target.chan1, AST_CEL_ATTENDEDTRANSFER, NULL, transferer_linkedid, target.chan2);
- restart_session_timer(p);
- if (p->stimer->st_expirys > 0) {
- p->stimer->st_expirys--;
- }
+ /* Tell transferer that we're done. */
+ transmit_notify_with_sipfrag(transferer, seqno, "200 OK", TRUE);
+ append_history(transferer, "Xfer", "Refer succeeded");
+ transferer->refer->status = REFER_200OK;
+ if (target.chan2 && !ast_strlen_zero(xfersound) && ast_streamfile(target.chan2, xfersound, target.chan2->language) >= 0) {
+ ast_waitstream(target.chan2, "");
}
- }
- if (!req->ignore && p)
- p->lastinvite = seqno;
+ /* By forcing the masquerade, we know that target.chan1 and target.chan2 are bridged. We then
+ * can queue connected line updates where they need to go.
+ *
+ * before a masquerade, all channel and pvt locks must be unlocked. Any recursive
+ * channel locks held before this function invalidates channel container locking order.
+ * Since we are unlocking both the pvt (transferer) and its owner channel (current.chan1)
+ * it is possible for current.chan1 to be destroyed in the pbx thread. To prevent this
+ * we must give c a reference before any unlocking takes place.
+ */
- if (replace_id) { /* Attended transfer or call pickup - we're the target */
- if (!ast_strlen_zero(pickup.exten)) {
- append_history(p, "Xfer", "INVITE/Replace received");
+ ast_channel_ref(current->chan1);
+ ast_channel_unlock(current->chan1); /* current.chan1 is p->owner before the masq, it was locked by socket_read()*/
+ ast_channel_unlock(target.chan1);
+ *nounlock = 1; /* we just unlocked the dialog's channel and have no plans of locking it again. */
+ sip_pvt_unlock(targetcall_pvt);
+ sip_pvt_unlock(transferer);
- /* Let the caller know we're giving it a shot */
- transmit_response(p, "100 Trying", req);
- ast_setstate(c, AST_STATE_RING);
+ ast_do_masquerade(target.chan1);
- /* Do the pickup itself */
- ast_channel_unlock(c);
- *nounlock = 1;
+ ast_channel_lock(transferer); /* the transferer pvt is expected to remain locked on return */
- /* since p->owner (c) is unlocked, we need to go ahead and unlock pvt for both
- * magic pickup and ast_hangup. Both of these functions will attempt to lock
- * p->owner again, which can cause a deadlock if we already hold a lock on p.
- * Locking order is, channel then pvt. Dead lock avoidance must be used if
- * called the other way around. */
- sip_pvt_unlock(p);
- do_magic_pickup(c, pickup.exten, pickup.context);
- /* Now we're either masqueraded or we failed to pickup, in either case we... */
- ast_hangup(c);
- sip_pvt_lock(p); /* pvt is expected to remain locked on return, so re-lock it */
+ ast_indicate(target.chan1, AST_CONTROL_UNHOLD);
- res = 0;
- goto request_invite_cleanup;
+ if (target.chan2) {
+ ast_channel_queue_connected_line_update(target.chan1, &connected_to_transferee);
+ ast_channel_queue_connected_line_update(target.chan2, &connected_to_target);
} else {
- /* Go and take over the target call */
- if (sipdebug)
- ast_debug(4, "Sending this call to the invite/replcaes handler %s\n", p->callid);
- res = handle_invite_replaces(p, req, debug, seqno, sin, nounlock);
- refer_locked = 0;
- goto request_invite_cleanup;
+ /* Since target.chan1 isn't actually connected to another channel, there is no way for us
+ * to queue a frame so that its connected line status will be updated. Instead, we have to
+ * change it directly. Since we are not the channel thread, we cannot run a connected line
+ * interception macro on target.chan1
+ */
+ ast_channel_update_connected_line(target.chan1, &connected_to_target);
}
+ ast_channel_unref(current->chan1);
}
+ /* at this point if the transfer is successful only the transferer pvt should be locked. */
+ ast_party_connected_line_free(&connected_to_target);
+ ast_party_connected_line_free(&connected_to_transferee);
+ if (targetcall_pvt)
+ ao2_t_ref(targetcall_pvt, -1, "drop targetcall_pvt");
+ return 1;
+}
- if (c) { /* We have a call -either a new call or an old one (RE-INVITE) */
- enum ast_channel_state c_state = c->_state;
- if (c_state != AST_STATE_UP && reinvite &&
- (p->invitestate == INV_TERMINATED || p->invitestate == INV_CONFIRMED)) {
- /* If these conditions are true, and the channel is still in the 'ringing'
- * state, then this likely means that we have a situation where the initial
- * INVITE transaction has completed *but* the channel's state has not yet been
- * changed to UP. The reason this could happen is if the reinvite is received
- * on the SIP socket prior to an application calling ast_read on this channel
- * to read the answer frame we earlier queued on it. In this case, the reinvite
- * is completely legitimate so we need to handle this the same as if the channel
- * were already UP. Thus we are purposely falling through to the AST_STATE_UP case.
- */
- c_state = AST_STATE_UP;
- }
+/*! \brief Handle incoming REFER request */
+/*! \page SIP_REFER SIP transfer Support (REFER)
- switch(c_state) {
- case AST_STATE_DOWN:
- ast_debug(2, "%s: New call is still down.... Trying... \n", c->name);
- transmit_provisional_response(p, "100 Trying", req, 0);
- p->invitestate = INV_PROCEEDING;
- ast_setstate(c, AST_STATE_RING);
- if (strcmp(p->exten, ast_pickup_ext())) { /* Call to extension -start pbx on this call */
- enum ast_pbx_result result;
+ REFER is used for call transfer in SIP. We get a REFER
+ to place a new call with an INVITE somwhere and then
+ keep the transferor up-to-date of the transfer. If the
+ transfer fails, get back on line with the orginal call.
- result = ast_pbx_start(c);
+ - REFER can be sent outside or inside of a dialog.
+ Asterisk only accepts REFER inside of a dialog.
- switch(result) {
- case AST_PBX_FAILED:
- ast_log(LOG_WARNING, "Failed to start PBX :(\n");
- p->invitestate = INV_COMPLETED;
- transmit_response_reliable(p, "503 Unavailable", req);
- break;
- case AST_PBX_CALL_LIMIT:
- ast_log(LOG_WARNING, "Failed to start PBX (call limit reached) \n");
- p->invitestate = INV_COMPLETED;
- transmit_response_reliable(p, "480 Temporarily Unavailable", req);
- break;
- case AST_PBX_SUCCESS:
- /* nothing to do */
- break;
- }
+ - If we get a replaces header, it is an attended transfer
- if (result) {
+ \par Blind transfers
+ The transferor provides the transferee
+ with the transfer targets contact. The signalling between
+ transferer or transferee should not be cancelled, so the
+ call is recoverable if the transfer target can not be reached
+ by the transferee.
- /* Unlock locks so ast_hangup can do its magic */
- ast_channel_unlock(c);
- sip_pvt_unlock(p);
- ast_hangup(c);
- sip_pvt_lock(p);
- c = NULL;
- }
- } else { /* Pickup call in call group */
- ast_channel_unlock(c);
- *nounlock = 1;
- if (ast_pickup_call(c)) {
- ast_log(LOG_NOTICE, "Nothing to pick up for %s\n", p->callid);
- transmit_response_reliable(p, "503 Unavailable", req);
- sip_alreadygone(p);
- /* Unlock locks so ast_hangup can do its magic */
- sip_pvt_unlock(p);
- c->hangupcause = AST_CAUSE_CALL_REJECTED;
- } else {
- sip_pvt_unlock(p);
- c->hangupcause = AST_CAUSE_NORMAL_CLEARING;
- }
- p->invitestate = INV_COMPLETED;
- ast_hangup(c);
- sip_pvt_lock(p);
- c = NULL;
- }
- break;
- case AST_STATE_RING:
- transmit_provisional_response(p, "100 Trying", req, 0);
- p->invitestate = INV_PROCEEDING;
- break;
- case AST_STATE_RINGING:
- transmit_provisional_response(p, "180 Ringing", req, 0);
- p->invitestate = INV_PROCEEDING;
- break;
- case AST_STATE_UP:
- ast_debug(2, "%s: This call is UP.... \n", c->name);
+ In this case, Asterisk receives a TRANSFER from
+ the transferor, thus is the transferee. We should
+ try to set up a call to the contact provided
+ and if that fails, re-connect the current session.
+ If the new call is set up, we issue a hangup.
+ In this scenario, we are following section 5.2
+ in the SIP CC Transfer draft. (Transfer without
+ a GRUU)
+
+ \par Transfer with consultation hold
+ In this case, the transferor
+ talks to the transfer target before the transfer takes place.
+ This is implemented with SIP hold and transfer.
+ Note: The invite From: string could indicate a transfer.
+ (Section 6. Transfer with consultation hold)
+ The transferor places the transferee on hold, starts a call
+ with the transfer target to alert them to the impending
+ transfer, terminates the connection with the target, then
+ proceeds with the transfer (as in Blind transfer above)
+
+ \par Attended transfer
+ The transferor places the transferee
+ on hold, calls the transfer target to alert them,
+ places the target on hold, then proceeds with the transfer
+ using a Replaces header field in the Refer-to header. This
+ will force the transfee to send an Invite to the target,
+ with a replaces header that instructs the target to
+ hangup the call between the transferor and the target.
+ In this case, the Refer/to: uses the AOR address. (The same
+ URI that the transferee used to establish the session with
+ the transfer target (To: ). The Require: replaces header should
+ be in the INVITE to avoid the wrong UA in a forked SIP proxy
+ scenario to answer and have no call to replace with.
+
+ The referred-by header is *NOT* required, but if we get it,
+ can be copied into the INVITE to the transfer target to
+ inform the target about the transferor
+
+ "Any REFER request has to be appropriately authenticated.".
+
+ We can't destroy dialogs, since we want the call to continue.
+
+ */
+static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, int debug, int seqno, int *nounlock)
+{
+ struct sip_dual current; /* Chan1: Call between asterisk and transferer */
+ /* Chan2: Call between asterisk and transferee */
+
+ int res = 0;
+ struct ast_channel *chans[2];
+ current.req.data = NULL;
+
+ if (req->debug)
+ ast_verbose("Call %s got a SIP call transfer from %s: (REFER)!\n", p->callid, ast_test_flag(&p->flags[0], SIP_OUTGOING) ? "callee" : "caller");
+
+ if (!p->owner) {
+ /* This is a REFER outside of an existing SIP dialog */
+ /* We can't handle that, so decline it */
+ ast_debug(3, "Call %s: Declined REFER, outside of dialog...\n", p->callid);
+ transmit_response(p, "603 Declined (No dialog)", req);
+ if (!req->ignore) {
+ append_history(p, "Xfer", "Refer failed. Outside of dialog.");
+ sip_alreadygone(p);
+ pvt_set_needdestroy(p, "outside of dialog");
+ }
+ return 0;
+ }
+
+
+ /* Check if transfer is allowed from this device */
+ if (p->allowtransfer == TRANSFER_CLOSED ) {
+ /* Transfer not allowed, decline */
+ transmit_response(p, "603 Declined (policy)", req);
+ append_history(p, "Xfer", "Refer failed. Allowtransfer == closed.");
+ /* Do not destroy SIP session */
+ return 0;
+ }
- transmit_response(p, "100 Trying", req);
+ if (!req->ignore && ast_test_flag(&p->flags[0], SIP_GOTREFER)) {
+ /* Already have a pending REFER */
+ transmit_response(p, "491 Request pending", req);
+ append_history(p, "Xfer", "Refer failed. Request pending.");
+ return 0;
+ }
- if (p->t38.state == T38_PEER_REINVITE) {
- p->t38id = ast_sched_add(sched, 5000, sip_t38_abort, dialog_ref(p, "passing dialog ptr into sched structure based on t38id for sip_t38_abort."));
- } else if (p->t38.state == T38_ENABLED) {
- ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
- transmit_response_with_t38_sdp(p, "200 OK", req, (reinvite ? XMIT_RELIABLE : (req->ignore ? XMIT_UNRELIABLE : XMIT_CRITICAL)));
- } else if (p->t38.state == T38_DISABLED) {
- /* If this is not a re-invite or something to ignore - it's critical */
- ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
- transmit_response_with_sdp(p, "200 OK", req, (reinvite ? XMIT_RELIABLE : (req->ignore ? XMIT_UNRELIABLE : XMIT_CRITICAL)), p->session_modify == TRUE ? FALSE : TRUE, FALSE);
- }
+ /* Allocate memory for call transfer data */
+ if (!p->refer && !sip_refer_allocate(p)) {
+ transmit_response(p, "500 Internal Server Error", req);
+ append_history(p, "Xfer", "Refer failed. Memory allocation error.");
+ return -3;
+ }
- p->invitestate = INV_TERMINATED;
+ res = get_refer_info(p, req); /* Extract headers */
+
+ p->refer->status = REFER_SENT;
+
+ if (res != 0) {
+ switch (res) {
+ case -2: /* Syntax error */
+ transmit_response(p, "400 Bad Request (Refer-to missing)", req);
+ append_history(p, "Xfer", "Refer failed. Refer-to missing.");
+ if (req->debug)
+ ast_debug(1, "SIP transfer to black hole can't be handled (no refer-to: )\n");
+ break;
+ case -3:
+ transmit_response(p, "603 Declined (Non sip: uri)", req);
+ append_history(p, "Xfer", "Refer failed. Non SIP uri");
+ if (req->debug)
+ ast_debug(1, "SIP transfer to non-SIP uri denied\n");
break;
default:
- ast_log(LOG_WARNING, "Don't know how to handle INVITE in state %d\n", c->_state);
- transmit_response(p, "100 Trying", req);
+ /* Refer-to extension not found, fake a failed transfer */
+ transmit_response(p, "202 Accepted", req);
+ append_history(p, "Xfer", "Refer failed. Bad extension.");
+ transmit_notify_with_sipfrag(p, seqno, "404 Not found", TRUE);
+ ast_clear_flag(&p->flags[0], SIP_GOTREFER);
+ if (req->debug)
+ ast_debug(1, "SIP transfer to bad extension: %s\n", p->refer->refer_to);
break;
}
- } else {
- if (p && (p->autokillid == -1)) {
- const char *msg;
-
- if (!p->jointcapability)
- msg = "488 Not Acceptable Here (codec error)";
- else {
- ast_log(LOG_NOTICE, "Unable to create/find SIP channel for this INVITE\n");
- msg = "503 Unavailable";
- }
- transmit_response_reliable(p, msg, req);
- p->invitestate = INV_COMPLETED;
- sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
- }
+ return 0;
}
+ if (ast_strlen_zero(p->context))
+ ast_string_field_set(p, context, sip_cfg.default_context);
-request_invite_cleanup:
+ /* If we do not support SIP domains, all transfers are local */
+ if (sip_cfg.allow_external_domains && check_sip_domain(p->refer->refer_to_domain, NULL, 0)) {
+ p->refer->localtransfer = 1;
+ if (sipdebug)
+ ast_debug(3, "This SIP transfer is local : %s\n", p->refer->refer_to_domain);
+ } else if (AST_LIST_EMPTY(&domain_list) || check_sip_domain(p->refer->refer_to_domain, NULL, 0)) {
+ /* This PBX doesn't bother with SIP domains or domain is local, so this transfer is local */
+ p->refer->localtransfer = 1;
+ } else if (sipdebug)
+ ast_debug(3, "This SIP transfer is to a remote SIP extension (remote domain %s)\n", p->refer->refer_to_domain);
- if (refer_locked && p->refer && p->refer->refer_call) {
- sip_pvt_unlock(p->refer->refer_call);
- if (p->refer->refer_call->owner) {
- ast_channel_unlock(p->refer->refer_call->owner);
- }
+ /* Is this a repeat of a current request? Ignore it */
+ /* Don't know what else to do right now. */
+ if (req->ignore)
+ return res;
+
+ /* If this is a blind transfer, we have the following
+ channels to work with:
+ - chan1, chan2: The current call between transferer and transferee (2 channels)
+ - target_channel: A new call from the transferee to the target (1 channel)
+ We need to stay tuned to what happens in order to be able
+ to bring back the call to the transferer */
+
+ /* If this is a attended transfer, we should have all call legs within reach:
+ - chan1, chan2: The call between the transferer and transferee (2 channels)
+ - target_channel, targetcall_pvt: The call between the transferer and the target (2 channels)
+ We want to bridge chan2 with targetcall_pvt!
+
+ The replaces call id in the refer message points
+ to the call leg between Asterisk and the transferer.
+ So we need to connect the target and the transferee channel
+ and hangup the two other channels silently
+
+ If the target is non-local, the call ID could be on a remote
+ machine and we need to send an INVITE with replaces to the
+ target. We basically handle this as a blind transfer
+ and let the sip_call function catch that we need replaces
+ header in the INVITE.
+ */
+
+
+ /* Get the transferer's channel */
+ chans[0] = current.chan1 = p->owner;
+
+ /* Find the other part of the bridge (2) - transferee */
+ chans[1] = current.chan2 = ast_bridged_channel(current.chan1);
+
+ if (sipdebug)
+ ast_debug(3, "SIP %s transfer: Transferer channel %s, transferee channel %s\n", p->refer->attendedtransfer ? "attended" : "blind", current.chan1->name, current.chan2 ? current.chan2->name : "<none>");
+
+ if (!current.chan2 && !p->refer->attendedtransfer) {
+ /* No bridged channel, propably IVR or echo or similar... */
+ /* Guess we should masquerade or something here */
+ /* Until we figure it out, refuse transfer of such calls */
+ if (sipdebug)
+ ast_debug(3, "Refused SIP transfer on non-bridged channel.\n");
+ p->refer->status = REFER_FAILED;
+ append_history(p, "Xfer", "Refer failed. Non-bridged channel.");
+ transmit_response(p, "603 Declined", req);
+ return -1;
}
- return res;
-}
+ if (current.chan2) {
+ if (sipdebug)
+ ast_debug(4, "Got SIP transfer, applying to bridged peer '%s'\n", current.chan2->name);
-/*! \brief Find all call legs and bridge transferee with target
- * called from handle_request_refer
- *
- * \note this function assumes two locks to begin with, sip_pvt transferer and current.chan1 (the pvt's owner)...
- * 2 additional locks are held at the beginning of the function, targetcall_pvt, and targetcall_pvt's owner
- * channel (which is stored in target.chan1). These 2 locks _MUST_ be let go by the end of the function. Do
- * not be confused into thinking a pvt's owner is the same thing as the channels locked at the beginning of
- * this function, after the masquerade this may not be true. Be consistent and unlock only the exact same
- * pointers that were locked to begin with.
- *
- * If this function is successful, only the transferer pvt lock will remain on return. Setting nounlock indicates
- * to handle_request_do() that the pvt's owner it locked does not require an unlock.
- */
-static int local_attended_transfer(struct sip_pvt *transferer, struct sip_dual *current, struct sip_request *req, int seqno, int *nounlock)
-{
- struct sip_dual target; /* Chan 1: Call from tranferer to Asterisk */
- /* Chan 2: Call from Asterisk to target */
- int res = 0;
- struct sip_pvt *targetcall_pvt;
- struct ast_party_connected_line connected_to_transferee;
- struct ast_party_connected_line connected_to_target;
- char transferer_linkedid[32];
- struct ast_channel *chans[2];
+ ast_queue_control(current.chan1, AST_CONTROL_UNHOLD);
+ }
- /* Check if the call ID of the replaces header does exist locally */
- if (!(targetcall_pvt = get_sip_pvt_byid_locked(transferer->refer->replaces_callid, transferer->refer->replaces_callid_totag,
- transferer->refer->replaces_callid_fromtag))) {
- if (transferer->refer->localtransfer) {
- /* We did not find the refered call. Sorry, can't accept then */
- transmit_response(transferer, "202 Accepted", req);
- /* Let's fake a response from someone else in order
- to follow the standard */
- transmit_notify_with_sipfrag(transferer, seqno, "481 Call leg/transaction does not exist", TRUE);
- append_history(transferer, "Xfer", "Refer failed");
- ast_clear_flag(&transferer->flags[0], SIP_GOTREFER);
- transferer->refer->status = REFER_FAILED;
- return -1;
- }
+ ast_set_flag(&p->flags[0], SIP_GOTREFER);
+
+ /* Attended transfer: Find all call legs and bridge transferee with target*/
+ if (p->refer->attendedtransfer) {
+ if ((res = local_attended_transfer(p, ¤t, req, seqno, nounlock)))
+ return res; /* We're done with the transfer */
/* Fall through for remote transfers that we did not find locally */
- ast_debug(3, "SIP attended transfer: Not our call - generating INVITE with replaces\n");
- return 0;
+ if (sipdebug)
+ ast_debug(4, "SIP attended transfer: Still not our call - generating INVITE with replaces\n");
+ /* Fallthrough if we can't find the call leg internally */
}
- /* Ok, we can accept this transfer */
- transmit_response(transferer, "202 Accepted", req);
- append_history(transferer, "Xfer", "Refer accepted");
- if (!targetcall_pvt->owner) { /* No active channel */
- ast_debug(4, "SIP attended transfer: Error: No owner of target call\n");
- /* Cancel transfer */
- transmit_notify_with_sipfrag(transferer, seqno, "503 Service Unavailable", TRUE);
- append_history(transferer, "Xfer", "Refer failed");
- ast_clear_flag(&transferer->flags[0], SIP_GOTREFER);
- transferer->refer->status = REFER_FAILED;
- sip_pvt_unlock(targetcall_pvt);
- if (targetcall_pvt)
- ao2_t_ref(targetcall_pvt, -1, "Drop targetcall_pvt pointer");
- return -1;
+
+ /* Parking a call */
+ if (p->refer->localtransfer && !strcmp(p->refer->refer_to, ast_parking_ext())) {
+ /* Must release c's lock now, because it will not longer be accessible after the transfer! */
+ *nounlock = 1;
+ ast_channel_unlock(current.chan1);
+ copy_request(¤t.req, req);
+ ast_clear_flag(&p->flags[0], SIP_GOTREFER);
+ p->refer->status = REFER_200OK;
+ append_history(p, "Xfer", "REFER to call parking.");
+ ast_manager_event_multichan(EVENT_FLAG_CALL, "Transfer", 2, chans, "TransferMethod: SIP\r\nTransferType: Blind\r\nChannel: %s\r\nUniqueid: %s\r\nSIP-Callid: %s\r\nTargetChannel: %s\r\nTargetUniqueid: %s\r\nTransferExten: %s\r\nTransfer2Parking: Yes\r\n",
+ current.chan1->name,
+ current.chan1->uniqueid,
+ p->callid,
+ current.chan2->name,
+ current.chan2->uniqueid,
+ p->refer->refer_to);
+ if (sipdebug)
+ ast_debug(4, "SIP transfer to parking: trying to park %s. Parked by %s\n", current.chan2->name, current.chan1->name);
+ sip_park(current.chan2, current.chan1, req, seqno);
+ return res;
+ }
+
+ /* Blind transfers and remote attended xfers */
+ transmit_response(p, "202 Accepted", req);
+
+ if (current.chan1 && current.chan2) {
+ ast_debug(3, "chan1->name: %s\n", current.chan1->name);
+ pbx_builtin_setvar_helper(current.chan1, "BLINDTRANSFER", current.chan2->name);
+ }
+ if (current.chan2) {
+ pbx_builtin_setvar_helper(current.chan2, "BLINDTRANSFER", current.chan1->name);
+ pbx_builtin_setvar_helper(current.chan2, "SIPDOMAIN", p->refer->refer_to_domain);
+ pbx_builtin_setvar_helper(current.chan2, "SIPTRANSFER", "yes");
+ /* One for the new channel */
+ pbx_builtin_setvar_helper(current.chan2, "_SIPTRANSFER", "yes");
+ /* Attended transfer to remote host, prepare headers for the INVITE */
+ if (p->refer->referred_by)
+ pbx_builtin_setvar_helper(current.chan2, "_SIPTRANSFER_REFERER", p->refer->referred_by);
+ }
+ /* Generate a Replaces string to be used in the INVITE during attended transfer */
+ if (!ast_strlen_zero(p->refer->replaces_callid)) {
+ char tempheader[SIPBUFSIZE];
+ snprintf(tempheader, sizeof(tempheader), "%s%s%s%s%s", p->refer->replaces_callid,
+ p->refer->replaces_callid_totag ? ";to-tag=" : "",
+ p->refer->replaces_callid_totag,
+ p->refer->replaces_callid_fromtag ? ";from-tag=" : "",
+ p->refer->replaces_callid_fromtag);
+ if (current.chan2)
+ pbx_builtin_setvar_helper(current.chan2, "_SIPTRANSFER_REPLACES", tempheader);
}
+ /* Must release lock now, because it will not longer
+ be accessible after the transfer! */
+ *nounlock = 1;
+ /*
+ * Increase ref count so that we can delay channel destruction until after
+ * we get a chance to fire off some events.
+ */
+ ast_channel_ref(current.chan1);
+ ast_channel_unlock(current.chan1);
- /* We have a channel, find the bridge */
- target.chan1 = targetcall_pvt->owner; /* Transferer to Asterisk */
- target.chan2 = ast_bridged_channel(targetcall_pvt->owner); /* Asterisk to target */
+ /* Connect the call */
- if (!target.chan2 || !(target.chan2->_state == AST_STATE_UP || target.chan2->_state == AST_STATE_RINGING) ) {
- /* Wrong state of new channel */
- if (target.chan2)
- ast_debug(4, "SIP attended transfer: Error: Wrong state of target call: %s\n", ast_state2str(target.chan2->_state));
- else if (target.chan1->_state != AST_STATE_RING)
- ast_debug(4, "SIP attended transfer: Error: No target channel\n");
- else
- ast_debug(4, "SIP attended transfer: Attempting transfer in ringing state\n");
- }
+ /* FAKE ringing if not attended transfer */
+ if (!p->refer->attendedtransfer)
+ transmit_notify_with_sipfrag(p, seqno, "183 Ringing", FALSE);
- /* Transfer */
- if (sipdebug) {
- if (current->chan2) /* We have two bridges */
- ast_debug(4, "SIP attended transfer: trying to bridge %s and %s\n", target.chan1->name, current->chan2->name);
- else /* One bridge, propably transfer of IVR/voicemail etc */
- ast_debug(4, "SIP attended transfer: trying to make %s take over (masq) %s\n", target.chan1->name, current->chan1->name);
+ /* For blind transfer, this will lead to a new call */
+ /* For attended transfer to remote host, this will lead to
+ a new SIP call with a replaces header, if the dial plan allows it
+ */
+ if (!current.chan2) {
+ /* We have no bridge, so we're talking with Asterisk somehow */
+ /* We need to masquerade this call */
+ /* What to do to fix this situation:
+ * Set up the new call in a new channel
+ * Let the new channel masq into this channel
+ Please add that code here :-)
+ */
+ p->refer->status = REFER_FAILED;
+ transmit_notify_with_sipfrag(p, seqno, "503 Service Unavailable (can't handle one-legged xfers)", TRUE);
+ ast_clear_flag(&p->flags[0], SIP_GOTREFER);
+ append_history(p, "Xfer", "Refer failed (only bridged calls).");
+ ast_channel_unref(current.chan1);
+ return -1;
}
+ ast_set_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Delay hangup */
- ast_set_flag(&transferer->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Delay hangup */
-
- ast_copy_string(transferer_linkedid, transferer->owner->linkedid, sizeof(transferer_linkedid));
- /* Perform the transfer */
- chans[0] = transferer->owner;
- chans[1] = target.chan1;
- ast_manager_event_multichan(EVENT_FLAG_CALL, "Transfer", 2, chans, "TransferMethod: SIP\r\nTransferType: Attended\r\nChannel: %s\r\nUniqueid: %s\r\nSIP-Callid: %s\r\nTargetChannel: %s\r\nTargetUniqueid: %s\r\n",
- transferer->owner->name,
- transferer->owner->uniqueid,
- transferer->callid,
- target.chan1->name,
- target.chan1->uniqueid);
- ast_party_connected_line_init(&connected_to_transferee);
- ast_party_connected_line_init(&connected_to_target);
- /* No need to lock current->chan1 here since it was locked in sipsock_read */
- ast_party_connected_line_copy(&connected_to_transferee, ¤t->chan1->connected);
- /* No need to lock target.chan1 here since it was locked in get_sip_pvt_byid_locked */
- ast_party_connected_line_copy(&connected_to_target, &target.chan1->connected);
- connected_to_target.source = connected_to_transferee.source = AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER;
- res = attempt_transfer(current, &target);
- if (res) {
- /* Failed transfer */
- transmit_notify_with_sipfrag(transferer, seqno, "486 Busy Here", TRUE);
- append_history(transferer, "Xfer", "Refer failed");
- ast_clear_flag(&transferer->flags[0], SIP_DEFER_BYE_ON_TRANSFER);
- /* if transfer failed, go ahead and unlock targetcall_pvt and it's owner channel */
- sip_pvt_unlock(targetcall_pvt);
- ast_channel_unlock(target.chan1);
- } else {
- /* Transfer succeeded! */
- const char *xfersound = pbx_builtin_getvar_helper(target.chan1, "ATTENDED_TRANSFER_COMPLETE_SOUND");
+ /* For blind transfers, move the call to the new extensions. For attended transfers on multiple
+ servers - generate an INVITE with Replaces. Either way, let the dial plan decided */
+ res = ast_async_goto(current.chan2, p->refer->refer_to_context, p->refer->refer_to, 1);
- /* target.chan1 was locked in get_sip_pvt_byid_locked, do not unlock target.chan1 before this */
- ast_cel_report_event(target.chan1, AST_CEL_ATTENDEDTRANSFER, NULL, transferer_linkedid, target.chan2);
+ if (!res) {
+ ast_manager_event_multichan(EVENT_FLAG_CALL, "Transfer", 2, chans, "TransferMethod: SIP\r\nTransferType: Blind\r\nChannel: %s\r\nUniqueid: %s\r\nSIP-Callid: %s\r\nTargetChannel: %s\r\nTargetUniqueid: %s\r\nTransferExten: %s\r\nTransferContext: %s\r\n",
+ current.chan1->name,
+ current.chan1->uniqueid,
+ p->callid,
+ current.chan2->name,
+ current.chan2->uniqueid,
+ p->refer->refer_to, p->refer->refer_to_context);
+ /* Success - we have a new channel */
+ ast_debug(3, "%s transfer succeeded. Telling transferer.\n", p->refer->attendedtransfer? "Attended" : "Blind");
- /* Tell transferer that we're done. */
- transmit_notify_with_sipfrag(transferer, seqno, "200 OK", TRUE);
- append_history(transferer, "Xfer", "Refer succeeded");
- transferer->refer->status = REFER_200OK;
- if (target.chan2 && !ast_strlen_zero(xfersound) && ast_streamfile(target.chan2, xfersound, target.chan2->language) >= 0) {
- ast_waitstream(target.chan2, "");
+ while (ast_channel_trylock(current.chan1)) {
+ sip_pvt_unlock(p);
+ sched_yield();
+ sip_pvt_lock(p);
}
- /* By forcing the masquerade, we know that target.chan1 and target.chan2 are bridged. We then
- * can queue connected line updates where they need to go.
- *
- * before a masquerade, all channel and pvt locks must be unlocked. Any recursive
- * channel locks held before this function invalidates channel container locking order.
- * Since we are unlocking both the pvt (transferer) and its owner channel (current.chan1)
- * it is possible for current.chan1 to be destroyed in the pbx thread. To prevent this
- * we must give c a reference before any unlocking takes place.
- */
+ /* XXX - what to we put in CEL 'extra' for attended transfers to external systems? NULL for now */
+ ast_cel_report_event(current.chan1, p->refer->attendedtransfer? AST_CEL_ATTENDEDTRANSFER : AST_CEL_BLINDTRANSFER, NULL, p->refer->attendedtransfer ? NULL : p->refer->refer_to, current.chan2);
+ ast_channel_unlock(current.chan1);
- ast_channel_ref(current->chan1);
- ast_channel_unlock(current->chan1); /* current.chan1 is p->owner before the masq, it was locked by socket_read()*/
- ast_channel_unlock(target.chan1);
- *nounlock = 1; /* we just unlocked the dialog's channel and have no plans of locking it again. */
- sip_pvt_unlock(targetcall_pvt);
- sip_pvt_unlock(transferer);
+ transmit_notify_with_sipfrag(p, seqno, "200 Ok", TRUE);
+ if (p->refer->localtransfer)
+ p->refer->status = REFER_200OK;
+ if (p->owner)
+ p->owner->hangupcause = AST_CAUSE_NORMAL_CLEARING;
+ append_history(p, "Xfer", "Refer succeeded.");
+ ast_clear_flag(&p->flags[0], SIP_GOTREFER);
+ /* Do not hangup call, the other side do that when we say 200 OK */
+ /* We could possibly implement a timer here, auto congestion */
+ res = 0;
+ } else {
+ ast_clear_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Don't delay hangup */
+ ast_debug(3, "%s transfer failed. Resuming original call.\n", p->refer->attendedtransfer? "Attended" : "Blind");
+ append_history(p, "Xfer", "Refer failed.");
+ /* Failure of some kind */
+ p->refer->status = REFER_FAILED;
+ transmit_notify_with_sipfrag(p, seqno, "503 Service Unavailable", TRUE);
+ ast_clear_flag(&p->flags[0], SIP_GOTREFER);
+ res = -1;
+ }
- ast_do_masquerade(target.chan1);
+ ast_channel_unref(current.chan1);
- ast_channel_lock(transferer); /* the transferer pvt is expected to remain locked on return */
+ return res;
+}
- ast_indicate(target.chan1, AST_CONTROL_UNHOLD);
+/*! \brief Handle incoming CANCEL request */
+static int handle_request_cancel(struct sip_pvt *p, struct sip_request *req)
+{
+
+ check_via(p, req);
+ sip_alreadygone(p);
- if (target.chan2) {
- ast_channel_queue_connected_line_update(target.chan1, &connected_to_transferee);
- ast_channel_queue_connected_line_update(target.chan2, &connected_to_target);
- } else {
- /* Since target.chan1 isn't actually connected to another channel, there is no way for us
- * to queue a frame so that its connected line status will be updated. Instead, we have to
- * change it directly. Since we are not the channel thread, we cannot run a connected line
- * interception macro on target.chan1
- */
- ast_channel_update_connected_line(target.chan1, &connected_to_target);
- }
- ast_channel_unref(current->chan1);
+ /* At this point, we could have cancelled the invite at the same time
+ as the other side sends a CANCEL. Our final reply with error code
+ might not have been received by the other side before the CANCEL
+ was sent, so let's just give up retransmissions and waiting for
+ ACK on our error code. The call is hanging up any way. */
+ if (p->invitestate == INV_TERMINATED)
+ __sip_pretend_ack(p);
+ else
+ p->invitestate = INV_CANCELLED;
+
+ if (p->owner && p->owner->_state == AST_STATE_UP) {
+ /* This call is up, cancel is ignored, we need a bye */
+ transmit_response(p, "200 OK", req);
+ ast_debug(1, "Got CANCEL on an answered call. Ignoring... \n");
+ return 0;
}
- /* at this point if the transfer is successful only the transferer pvt should be locked. */
- ast_party_connected_line_free(&connected_to_target);
- ast_party_connected_line_free(&connected_to_transferee);
- if (targetcall_pvt)
- ao2_t_ref(targetcall_pvt, -1, "drop targetcall_pvt");
- return 1;
+ if (ast_test_flag(&p->flags[0], SIP_INC_COUNT) || ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD))
+ update_call_counter(p, DEC_CALL_LIMIT);
+
+ stop_media_flows(p); /* Immediately stop RTP, VRTP and UDPTL as applicable */
+ if (p->owner) {
+ ast_set_hangupsource(p->owner, p->owner->name, 0);
+ ast_queue_hangup(p->owner);
+ }
+ else
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ if (p->initreq.len > 0) {
+ struct sip_pkt *pkt, *prev_pkt;
+ /* If the CANCEL we are receiving is a retransmission, and we already have scheduled
+ * a reliable 487, then we don't want to schedule another one on top of the previous
+ * one.
+ *
+ * As odd as this may sound, we can't rely on the previously-transmitted "reliable"
+ * response in this situation. What if we've sent all of our reliable responses
+ * already and now all of a sudden, we get this second CANCEL?
+ *
+ * The only way to do this correctly is to cancel our previously-scheduled reliably-
+ * transmitted response and send a new one in its place.
+ */
+ for (pkt = p->packets, prev_pkt = NULL; pkt; prev_pkt = pkt, pkt = pkt->next) {
+ if (pkt->seqno == p->lastinvite && pkt->response_code == 487) {
+ AST_SCHED_DEL(sched, pkt->retransid);
+ UNLINK(pkt, p->packets, prev_pkt);
+ ast_free(pkt);
+ break;
+ }
+ }
+ transmit_response_reliable(p, "487 Request Terminated", &p->initreq);
+ transmit_response(p, "200 OK", req);
+ return 1;
+ } else {
+ transmit_response(p, "481 Call Leg Does Not Exist", req);
+ return 0;
+ }
}
+/*! \brief Handle incoming BYE request */
+static int handle_request_bye(struct sip_pvt *p, struct sip_request *req)
+{
+ struct ast_channel *c=NULL;
+ int res;
+ struct ast_channel *bridged_to;
+
+ /* If we have an INCOMING invite that we haven't answered, terminate that transaction */
+ if (p->pendinginvite && !ast_test_flag(&p->flags[0], SIP_OUTGOING) && !req->ignore) {
+ transmit_response_reliable(p, "487 Request Terminated", &p->initreq);
+ }
-/*! \brief Handle incoming REFER request */
-/*! \page SIP_REFER SIP transfer Support (REFER)
+ __sip_pretend_ack(p);
- REFER is used for call transfer in SIP. We get a REFER
- to place a new call with an INVITE somwhere and then
- keep the transferor up-to-date of the transfer. If the
- transfer fails, get back on line with the orginal call.
+ p->invitestate = INV_TERMINATED;
- - REFER can be sent outside or inside of a dialog.
- Asterisk only accepts REFER inside of a dialog.
+ copy_request(&p->initreq, req);
+ if (sipdebug)
+ ast_debug(1, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid);
+ check_via(p, req);
+ sip_alreadygone(p);
- - If we get a replaces header, it is an attended transfer
+ /* Get RTCP quality before end of call */
+ if (p->do_history || p->owner) {
+ char quality_buf[AST_MAX_USER_FIELD], *quality;
+ struct ast_channel *bridge = p->owner ? ast_bridged_channel(p->owner) : NULL;
- \par Blind transfers
- The transferor provides the transferee
- with the transfer targets contact. The signalling between
- transferer or transferee should not be cancelled, so the
- call is recoverable if the transfer target can not be reached
- by the transferee.
+ /* We need to get the lock on bridge because ast_rtp_instance_set_stats_vars will attempt
+ * to lock the bridge. This may get hairy...
+ */
+ while (bridge && ast_channel_trylock(bridge)) {
+ ast_channel_unlock(p->owner);
+ do {
+ /* Can't use DEADLOCK_AVOIDANCE since p is an ao2 object */
+ sip_pvt_unlock(p);
+ usleep(1);
+ sip_pvt_lock(p);
+ } while (p->owner && ast_channel_trylock(p->owner));
+ bridge = p->owner ? ast_bridged_channel(p->owner) : NULL;
+ }
- In this case, Asterisk receives a TRANSFER from
- the transferor, thus is the transferee. We should
- try to set up a call to the contact provided
- and if that fails, re-connect the current session.
- If the new call is set up, we issue a hangup.
- In this scenario, we are following section 5.2
- in the SIP CC Transfer draft. (Transfer without
- a GRUU)
- \par Transfer with consultation hold
- In this case, the transferor
- talks to the transfer target before the transfer takes place.
- This is implemented with SIP hold and transfer.
- Note: The invite From: string could indicate a transfer.
- (Section 6. Transfer with consultation hold)
- The transferor places the transferee on hold, starts a call
- with the transfer target to alert them to the impending
- transfer, terminates the connection with the target, then
- proceeds with the transfer (as in Blind transfer above)
+ if (p->rtp && (quality = ast_rtp_instance_get_quality(p->rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality_buf, sizeof(quality_buf)))) {
+ if (p->do_history) {
+ append_history(p, "RTCPaudio", "Quality:%s", quality);
- \par Attended transfer
- The transferor places the transferee
- on hold, calls the transfer target to alert them,
- places the target on hold, then proceeds with the transfer
- using a Replaces header field in the Refer-to header. This
- will force the transfee to send an Invite to the target,
- with a replaces header that instructs the target to
- hangup the call between the transferor and the target.
- In this case, the Refer/to: uses the AOR address. (The same
- URI that the transferee used to establish the session with
- the transfer target (To: ). The Require: replaces header should
- be in the INVITE to avoid the wrong UA in a forked SIP proxy
- scenario to answer and have no call to replace with.
+ if ((quality = ast_rtp_instance_get_quality(p->rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_JITTER, quality_buf, sizeof(quality_buf)))) {
+ append_history(p, "RTCPaudioJitter", "Quality:%s", quality);
+ }
+ if ((quality = ast_rtp_instance_get_quality(p->rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_LOSS, quality_buf, sizeof(quality_buf)))) {
+ append_history(p, "RTCPaudioLoss", "Quality:%s", quality);
+ }
+ if ((quality = ast_rtp_instance_get_quality(p->rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT, quality_buf, sizeof(quality_buf)))) {
+ append_history(p, "RTCPaudioRTT", "Quality:%s", quality);
+ }
+ }
- The referred-by header is *NOT* required, but if we get it,
- can be copied into the INVITE to the transfer target to
- inform the target about the transferor
+ if (p->owner) {
+ ast_rtp_instance_set_stats_vars(p->owner, p->rtp);
+ }
- "Any REFER request has to be appropriately authenticated.".
-
- We can't destroy dialogs, since we want the call to continue.
-
- */
-static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, int debug, int seqno, int *nounlock)
-{
- struct sip_dual current; /* Chan1: Call between asterisk and transferer */
- /* Chan2: Call between asterisk and transferee */
+ }
- int res = 0;
- struct ast_channel *chans[2];
- current.req.data = NULL;
+ if (bridge) {
+ struct sip_pvt *q = bridge->tech_pvt;
- if (req->debug)
- ast_verbose("Call %s got a SIP call transfer from %s: (REFER)!\n", p->callid, ast_test_flag(&p->flags[0], SIP_OUTGOING) ? "callee" : "caller");
+ if (IS_SIP_TECH(bridge->tech) && q && q->rtp) {
+ ast_rtp_instance_set_stats_vars(bridge, q->rtp);
+ }
+ ast_channel_unlock(bridge);
+ }
- if (!p->owner) {
- /* This is a REFER outside of an existing SIP dialog */
- /* We can't handle that, so decline it */
- ast_debug(3, "Call %s: Declined REFER, outside of dialog...\n", p->callid);
- transmit_response(p, "603 Declined (No dialog)", req);
- if (!req->ignore) {
- append_history(p, "Xfer", "Refer failed. Outside of dialog.");
- sip_alreadygone(p);
- pvt_set_needdestroy(p, "outside of dialog");
+ if (p->vrtp && (quality = ast_rtp_instance_get_quality(p->vrtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality_buf, sizeof(quality_buf)))) {
+ if (p->do_history) {
+ append_history(p, "RTCPvideo", "Quality:%s", quality);
+ }
+ if (p->owner) {
+ pbx_builtin_setvar_helper(p->owner, "RTPVIDEOQOS", quality);
+ }
+ }
+ if (p->trtp && (quality = ast_rtp_instance_get_quality(p->trtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality_buf, sizeof(quality_buf)))) {
+ if (p->do_history) {
+ append_history(p, "RTCPtext", "Quality:%s", quality);
+ }
+ if (p->owner) {
+ pbx_builtin_setvar_helper(p->owner, "RTPTEXTQOS", quality);
+ }
}
- return 0;
}
+ stop_media_flows(p); /* Immediately stop RTP, VRTP and UDPTL as applicable */
+ stop_session_timer(p); /* Stop Session-Timer */
- /* Check if transfer is allowed from this device */
- if (p->allowtransfer == TRANSFER_CLOSED ) {
- /* Transfer not allowed, decline */
- transmit_response(p, "603 Declined (policy)", req);
- append_history(p, "Xfer", "Refer failed. Allowtransfer == closed.");
- /* Do not destroy SIP session */
- return 0;
+ if (!ast_strlen_zero(get_header(req, "Also"))) {
+ ast_log(LOG_NOTICE, "Client '%s' using deprecated BYE/Also transfer method. Ask vendor to support REFER instead\n",
+ ast_inet_ntoa(p->recv.sin_addr));
+ if (ast_strlen_zero(p->context))
+ ast_string_field_set(p, context, sip_cfg.default_context);
+ res = get_also_info(p, req);
+ if (!res) {
+ c = p->owner;
+ if (c) {
+ bridged_to = ast_bridged_channel(c);
+ if (bridged_to) {
+ /* Don't actually hangup here... */
+ ast_queue_control(c, AST_CONTROL_UNHOLD);
+ ast_channel_unlock(c); /* async_goto can do a masquerade, no locks can be held during a masq */
+ ast_async_goto(bridged_to, p->context, p->refer->refer_to, 1);
+ ast_channel_lock(c);
+ } else
+ ast_queue_hangup(p->owner);
+ }
+ } else {
+ ast_log(LOG_WARNING, "Invalid transfer information from '%s'\n", ast_inet_ntoa(p->recv.sin_addr));
+ if (p->owner)
+ ast_queue_hangup_with_cause(p->owner, AST_CAUSE_PROTOCOL_ERROR);
+ }
+ } else if (p->owner) {
+ ast_set_hangupsource(p->owner, p->owner->name, 0);
+ ast_queue_hangup(p->owner);
+ ast_debug(3, "Received bye, issuing owner hangup\n");
+ } else {
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ ast_debug(3, "Received bye, no owner, selfdestruct soon.\n");
}
+ ast_clear_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
+ transmit_response(p, "200 OK", req);
- if (!req->ignore && ast_test_flag(&p->flags[0], SIP_GOTREFER)) {
- /* Already have a pending REFER */
- transmit_response(p, "491 Request pending", req);
- append_history(p, "Xfer", "Refer failed. Request pending.");
- return 0;
- }
+ return 1;
+}
- /* Allocate memory for call transfer data */
- if (!p->refer && !sip_refer_allocate(p)) {
- transmit_response(p, "500 Internal Server Error", req);
- append_history(p, "Xfer", "Refer failed. Memory allocation error.");
- return -3;
- }
+/*! \brief Handle incoming MESSAGE request */
+static int handle_request_message(struct sip_pvt *p, struct sip_request *req)
+{
+ if (!req->ignore) {
+ if (req->debug)
+ ast_verbose("Receiving message!\n");
+ receive_message(p, req);
+ } else
+ transmit_response(p, "202 Accepted", req);
+ return 1;
+}
- res = get_refer_info(p, req); /* Extract headers */
+static enum sip_publish_type determine_sip_publish_type(struct sip_request *req, const char * const event, const char * const etag, const char * const expires, int *expires_int)
+{
+ int etag_present = !ast_strlen_zero(etag);
+ int body_present = req->lines > 0;
- p->refer->status = REFER_SENT;
+ ast_assert(expires_int != NULL);
- if (res != 0) {
- switch (res) {
- case -2: /* Syntax error */
- transmit_response(p, "400 Bad Request (Refer-to missing)", req);
- append_history(p, "Xfer", "Refer failed. Refer-to missing.");
- if (req->debug)
- ast_debug(1, "SIP transfer to black hole can't be handled (no refer-to: )\n");
- break;
- case -3:
- transmit_response(p, "603 Declined (Non sip: uri)", req);
- append_history(p, "Xfer", "Refer failed. Non SIP uri");
- if (req->debug)
- ast_debug(1, "SIP transfer to non-SIP uri denied\n");
- break;
- default:
- /* Refer-to extension not found, fake a failed transfer */
- transmit_response(p, "202 Accepted", req);
- append_history(p, "Xfer", "Refer failed. Bad extension.");
- transmit_notify_with_sipfrag(p, seqno, "404 Not found", TRUE);
- ast_clear_flag(&p->flags[0], SIP_GOTREFER);
- if (req->debug)
- ast_debug(1, "SIP transfer to bad extension: %s\n", p->refer->refer_to);
- break;
- }
- return 0;
+ if (ast_strlen_zero(expires)) {
+ /* Section 6, item 4, second bullet point of RFC 3903 says to
+ * use a locally-configured default expiration if none is provided
+ * in the request
+ */
+ *expires_int = DEFAULT_PUBLISH_EXPIRES;
+ } else if (sscanf(expires, "%30d", expires_int) != 1) {
+ return SIP_PUBLISH_UNKNOWN;
}
- if (ast_strlen_zero(p->context))
- ast_string_field_set(p, context, sip_cfg.default_context);
-
- /* If we do not support SIP domains, all transfers are local */
- if (sip_cfg.allow_external_domains && check_sip_domain(p->refer->refer_to_domain, NULL, 0)) {
- p->refer->localtransfer = 1;
- if (sipdebug)
- ast_debug(3, "This SIP transfer is local : %s\n", p->refer->refer_to_domain);
- } else if (AST_LIST_EMPTY(&domain_list) || check_sip_domain(p->refer->refer_to_domain, NULL, 0)) {
- /* This PBX doesn't bother with SIP domains or domain is local, so this transfer is local */
- p->refer->localtransfer = 1;
- } else if (sipdebug)
- ast_debug(3, "This SIP transfer is to a remote SIP extension (remote domain %s)\n", p->refer->refer_to_domain);
- /* Is this a repeat of a current request? Ignore it */
- /* Don't know what else to do right now. */
- if (req->ignore)
- return res;
+ if (*expires_int == 0) {
+ return SIP_PUBLISH_REMOVE;
+ } else if (!etag_present && body_present) {
+ return SIP_PUBLISH_INITIAL;
+ } else if (etag_present && !body_present) {
+ return SIP_PUBLISH_REFRESH;
+ } else if (etag_present && body_present) {
+ return SIP_PUBLISH_MODIFY;
+ }
- /* If this is a blind transfer, we have the following
- channels to work with:
- - chan1, chan2: The current call between transferer and transferee (2 channels)
- - target_channel: A new call from the transferee to the target (1 channel)
- We need to stay tuned to what happens in order to be able
- to bring back the call to the transferer */
+ return SIP_PUBLISH_UNKNOWN;
+}
- /* If this is a attended transfer, we should have all call legs within reach:
- - chan1, chan2: The call between the transferer and transferee (2 channels)
- - target_channel, targetcall_pvt: The call between the transferer and the target (2 channels)
- We want to bridge chan2 with targetcall_pvt!
-
- The replaces call id in the refer message points
- to the call leg between Asterisk and the transferer.
- So we need to connect the target and the transferee channel
- and hangup the two other channels silently
-
- If the target is non-local, the call ID could be on a remote
- machine and we need to send an INVITE with replaces to the
- target. We basically handle this as a blind transfer
- and let the sip_call function catch that we need replaces
- header in the INVITE.
- */
+#ifdef HAVE_LIBXML2
+static void get_pidf_body(struct sip_request *req, char *pidf_body, size_t size)
+{
+ int i;
+ struct ast_str *str = ast_str_alloca(size);
+ for (i = 0; i < req->lines; ++i) {
+ ast_str_append(&str, 0, "%s", REQ_OFFSET_TO_STR(req, line[i]));
+ }
+ ast_copy_string(pidf_body, ast_str_buffer(str), size);
+}
+static int pidf_validate_tuple(struct ast_xml_node *tuple_node)
+{
+ const char *id;
+ int status_found = FALSE;
+ struct ast_xml_node *tuple_children;
+ struct ast_xml_node *tuple_children_iterator;
+ /* Tuples have to have an id attribute or they're invalid */
+ if (!(id = ast_xml_get_attribute(tuple_node, "id"))) {
+ ast_log(LOG_WARNING, "Tuple XML element has no attribute 'id'\n");
+ return FALSE;
+ }
+ /* We don't care what it actually is, just that it's there */
+ ast_xml_free_attr(id);
+ /* This is a tuple. It must have a status element */
+ if (!(tuple_children = ast_xml_node_get_children(tuple_node))) {
+ /* The tuple has no children. It sucks */
+ ast_log(LOG_WARNING, "Tuple XML element has no child elements\n");
+ return FALSE;
+ }
+ for (tuple_children_iterator = tuple_children; tuple_children_iterator;
+ tuple_children_iterator = ast_xml_node_get_next(tuple_children_iterator)) {
+ /* Similar to the wording used regarding tuples, the status element should appear
+ * first. However, we will once again relax things and accept the status at any
+ * position. We will enforce that only a single status element can be present.
+ */
+ if (strcmp(ast_xml_node_get_name(tuple_children_iterator), "status")) {
+ /* Not the status, we don't care */
+ continue;
+ }
+ if (status_found == TRUE) {
+ /* THERE CAN BE ONLY ONE!!! */
+ ast_log(LOG_WARNING, "Multiple status elements found in tuple. Only one allowed\n");
+ return FALSE;
+ }
+ status_found = TRUE;
+ }
+ return status_found;
+}
- /* Get the transferer's channel */
- chans[0] = current.chan1 = p->owner;
- /* Find the other part of the bridge (2) - transferee */
- chans[1] = current.chan2 = ast_bridged_channel(current.chan1);
+static int pidf_validate_presence(struct ast_xml_doc *doc)
+{
+ struct ast_xml_node *presence_node = ast_xml_get_root(doc);
+ struct ast_xml_node *child_nodes;
+ struct ast_xml_node *node_iterator;
+ struct ast_xml_ns *ns;
+ const char *entity;
+ const char *namespace;
+ const char presence_namespace[] = "urn:ietf:params:xml:ns:pidf";
- if (sipdebug)
- ast_debug(3, "SIP %s transfer: Transferer channel %s, transferee channel %s\n", p->refer->attendedtransfer ? "attended" : "blind", current.chan1->name, current.chan2 ? current.chan2->name : "<none>");
+ if (!presence_node) {
+ ast_log(LOG_WARNING, "Unable to retrieve root node of the XML document\n");
+ return FALSE;
+ }
+ /* Okay, we managed to open the document! YAY! Now, let's start making sure it's all PIDF-ified
+ * correctly.
+ */
+ if (strcmp(ast_xml_node_get_name(presence_node), "presence")) {
+ ast_log(LOG_WARNING, "Root node of PIDF document is not 'presence'. Invalid\n");
+ return FALSE;
+ }
- if (!current.chan2 && !p->refer->attendedtransfer) {
- /* No bridged channel, propably IVR or echo or similar... */
- /* Guess we should masquerade or something here */
- /* Until we figure it out, refuse transfer of such calls */
- if (sipdebug)
- ast_debug(3, "Refused SIP transfer on non-bridged channel.\n");
- p->refer->status = REFER_FAILED;
- append_history(p, "Xfer", "Refer failed. Non-bridged channel.");
- transmit_response(p, "603 Declined", req);
- return -1;
+ /* The presence element must have an entity attribute and an xmlns attribute. Furthermore
+ * the xmlns attribute must be "urn:ietf:params:xml:ns:pidf"
+ */
+ if (!(entity = ast_xml_get_attribute(presence_node, "entity"))) {
+ ast_log(LOG_WARNING, "Presence element of PIDF document has no 'entity' attribute\n");
+ return FALSE;
}
+ /* We're not interested in what the entity is, just that it exists */
+ ast_xml_free_attr(entity);
- if (current.chan2) {
- if (sipdebug)
- ast_debug(4, "Got SIP transfer, applying to bridged peer '%s'\n", current.chan2->name);
+ if (!(ns = ast_xml_find_namespace(doc, presence_node, NULL))) {
+ ast_log(LOG_WARNING, "Couldn't find default namespace...\n");
+ return FALSE;
+ }
- ast_queue_control(current.chan1, AST_CONTROL_UNHOLD);
+ namespace = ast_xml_get_ns_href(ns);
+ if (ast_strlen_zero(namespace) || strcmp(namespace, presence_namespace)) {
+ ast_log(LOG_WARNING, "PIDF document has invalid namespace value %s\n", namespace);
+ return FALSE;
}
- ast_set_flag(&p->flags[0], SIP_GOTREFER);
+ if (!(child_nodes = ast_xml_node_get_children(presence_node))) {
+ ast_log(LOG_WARNING, "PIDF document has no elements as children of 'presence'. Invalid\n");
+ return FALSE;
+ }
- /* Attended transfer: Find all call legs and bridge transferee with target*/
- if (p->refer->attendedtransfer) {
- if ((res = local_attended_transfer(p, ¤t, req, seqno, nounlock)))
- return res; /* We're done with the transfer */
- /* Fall through for remote transfers that we did not find locally */
- if (sipdebug)
- ast_debug(4, "SIP attended transfer: Still not our call - generating INVITE with replaces\n");
- /* Fallthrough if we can't find the call leg internally */
+ /* Check for tuple elements. RFC 3863 says that PIDF documents can have any number of
+ * tuples, including 0. The big thing here is that if there are tuple elements present,
+ * they have to have a single status element within.
+ *
+ * The RFC is worded such that tuples should appear as the first elements as children of
+ * the presence element. However, we'll be accepting of documents which may place other elements
+ * before the tuple(s).
+ */
+ for (node_iterator = child_nodes; node_iterator;
+ node_iterator = ast_xml_node_get_next(node_iterator)) {
+ if (strcmp(ast_xml_node_get_name(node_iterator), "tuple")) {
+ /* Not a tuple. We don't give a rat's hind quarters */
+ continue;
+ }
+ if (pidf_validate_tuple(node_iterator) == FALSE) {
+ ast_log(LOG_WARNING, "Unable to validate tuple\n");
+ return FALSE;
+ }
}
+ return TRUE;
+}
- /* Parking a call */
- if (p->refer->localtransfer && !strcmp(p->refer->refer_to, ast_parking_ext())) {
- /* Must release c's lock now, because it will not longer be accessible after the transfer! */
- *nounlock = 1;
- ast_channel_unlock(current.chan1);
- copy_request(¤t.req, req);
- ast_clear_flag(&p->flags[0], SIP_GOTREFER);
- p->refer->status = REFER_200OK;
- append_history(p, "Xfer", "REFER to call parking.");
- ast_manager_event_multichan(EVENT_FLAG_CALL, "Transfer", 2, chans, "TransferMethod: SIP\r\nTransferType: Blind\r\nChannel: %s\r\nUniqueid: %s\r\nSIP-Callid: %s\r\nTargetChannel: %s\r\nTargetUniqueid: %s\r\nTransferExten: %s\r\nTransfer2Parking: Yes\r\n",
- current.chan1->name,
- current.chan1->uniqueid,
- p->callid,
- current.chan2->name,
- current.chan2->uniqueid,
- p->refer->refer_to);
- if (sipdebug)
- ast_debug(4, "SIP transfer to parking: trying to park %s. Parked by %s\n", current.chan2->name, current.chan1->name);
- sip_park(current.chan2, current.chan1, req, seqno);
- return res;
+/*!
+ * \brief Makes sure that body is properly formatted PIDF
+ *
+ * Specifically, we check that the document has a "presence" element
+ * at the root and that within that, there is at least one "tuple" element
+ * that contains a "status" element.
+ *
+ * XXX This function currently assumes a default namespace is used. Of course
+ * if you're not using a default namespace, you're probably a stupid jerk anyway.
+ *
+ * \param req The SIP request to check
+ * \param[out] pidf_doc The validated PIDF doc.
+ * \retval FALSE The XML was malformed or the basic PIDF structure was marred
+ * \retval TRUE The PIDF document is of a valid format
+ */
+static int sip_pidf_validate(struct sip_request *req, struct ast_xml_doc **pidf_doc)
+{
+ struct ast_xml_doc *doc;
+ int content_length;
+ const char *content_length_str = get_header(req, "Content-Length");
+ const char *content_type = get_header(req, "Content-Type");
+ char pidf_body[SIPBUFSIZE];
+ int res;
+
+ if (ast_strlen_zero(content_type) || strcmp(content_type, "application/pidf+xml")) {
+ ast_log(LOG_WARNING, "Content type is not PIDF\n");
+ return FALSE;
}
- /* Blind transfers and remote attended xfers */
- transmit_response(p, "202 Accepted", req);
+ if (ast_strlen_zero(content_length_str)) {
+ ast_log(LOG_WARNING, "No content length. Can't determine bounds of PIDF document\n");
+ return FALSE;
+ }
- if (current.chan1 && current.chan2) {
- ast_debug(3, "chan1->name: %s\n", current.chan1->name);
- pbx_builtin_setvar_helper(current.chan1, "BLINDTRANSFER", current.chan2->name);
+ if (sscanf(content_length_str, "%30d", &content_length) != 1) {
+ ast_log(LOG_WARNING, "Invalid content length provided\n");
+ return FALSE;
}
- if (current.chan2) {
- pbx_builtin_setvar_helper(current.chan2, "BLINDTRANSFER", current.chan1->name);
- pbx_builtin_setvar_helper(current.chan2, "SIPDOMAIN", p->refer->refer_to_domain);
- pbx_builtin_setvar_helper(current.chan2, "SIPTRANSFER", "yes");
- /* One for the new channel */
- pbx_builtin_setvar_helper(current.chan2, "_SIPTRANSFER", "yes");
- /* Attended transfer to remote host, prepare headers for the INVITE */
- if (p->refer->referred_by)
- pbx_builtin_setvar_helper(current.chan2, "_SIPTRANSFER_REFERER", p->refer->referred_by);
+
+ if (content_length > sizeof(pidf_body)) {
+ ast_log(LOG_WARNING, "Content length of PIDF document truncated to %d bytes\n", (int) sizeof(pidf_body));
+ content_length = sizeof(pidf_body);
}
- /* Generate a Replaces string to be used in the INVITE during attended transfer */
- if (!ast_strlen_zero(p->refer->replaces_callid)) {
- char tempheader[SIPBUFSIZE];
- snprintf(tempheader, sizeof(tempheader), "%s%s%s%s%s", p->refer->replaces_callid,
- p->refer->replaces_callid_totag ? ";to-tag=" : "",
- p->refer->replaces_callid_totag,
- p->refer->replaces_callid_fromtag ? ";from-tag=" : "",
- p->refer->replaces_callid_fromtag);
- if (current.chan2)
- pbx_builtin_setvar_helper(current.chan2, "_SIPTRANSFER_REPLACES", tempheader);
+
+ get_pidf_body(req, pidf_body, content_length);
+
+ if (!(doc = ast_xml_read_memory(pidf_body, content_length))) {
+ ast_log(LOG_WARNING, "Unable to open XML PIDF document. Is it malformed?\n");
+ return FALSE;
}
- /* Must release lock now, because it will not longer
- be accessible after the transfer! */
- *nounlock = 1;
- /*
- * Increase ref count so that we can delay channel destruction until after
- * we get a chance to fire off some events.
- */
- ast_channel_ref(current.chan1);
- ast_channel_unlock(current.chan1);
- /* Connect the call */
+ res = pidf_validate_presence(doc);
+ if (res == TRUE) {
+ *pidf_doc = doc;
+ } else {
+ ast_xml_close(doc);
+ }
+ return res;
+}
- /* FAKE ringing if not attended transfer */
- if (!p->refer->attendedtransfer)
- transmit_notify_with_sipfrag(p, seqno, "183 Ringing", FALSE);
+static int cc_esc_publish_handler(struct sip_pvt *pvt, struct sip_request *req, struct event_state_compositor *esc, struct sip_esc_entry *esc_entry)
+{
+ const char *uri = REQ_OFFSET_TO_STR(req, rlPart2);
+ struct ast_cc_agent *agent = find_sip_cc_agent_by_notify_uri(uri);
+ struct sip_cc_agent_pvt *agent_pvt;
+ struct ast_xml_doc *pidf_doc = NULL;
+ const char *basic_status = NULL;
+ struct ast_xml_node *presence_node;
+ struct ast_xml_node *presence_children;
+ struct ast_xml_node *tuple_node;
+ struct ast_xml_node *tuple_children;
+ struct ast_xml_node *status_node;
+ struct ast_xml_node *status_children;
+ struct ast_xml_node *basic_node;
+ int res = 0;
- /* For blind transfer, this will lead to a new call */
- /* For attended transfer to remote host, this will lead to
- a new SIP call with a replaces header, if the dial plan allows it
- */
- if (!current.chan2) {
- /* We have no bridge, so we're talking with Asterisk somehow */
- /* We need to masquerade this call */
- /* What to do to fix this situation:
- * Set up the new call in a new channel
- * Let the new channel masq into this channel
- Please add that code here :-)
- */
- p->refer->status = REFER_FAILED;
- transmit_notify_with_sipfrag(p, seqno, "503 Service Unavailable (can't handle one-legged xfers)", TRUE);
- ast_clear_flag(&p->flags[0], SIP_GOTREFER);
- append_history(p, "Xfer", "Refer failed (only bridged calls).");
- ast_channel_unref(current.chan1);
+ if (!agent) {
+ ast_log(LOG_WARNING, "Could not find agent using uri '%s'\n", uri);
+ transmit_response(pvt, "412 Conditional Request Failed", req);
return -1;
}
- ast_set_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Delay hangup */
+ agent_pvt = agent->private_data;
- /* For blind transfers, move the call to the new extensions. For attended transfers on multiple
- servers - generate an INVITE with Replaces. Either way, let the dial plan decided */
- res = ast_async_goto(current.chan2, p->refer->refer_to_context, p->refer->refer_to, 1);
+ if (sip_pidf_validate(req, &pidf_doc) == FALSE) {
+ res = -1;
+ goto cc_publish_cleanup;
+ }
- if (!res) {
- ast_manager_event_multichan(EVENT_FLAG_CALL, "Transfer", 2, chans, "TransferMethod: SIP\r\nTransferType: Blind\r\nChannel: %s\r\nUniqueid: %s\r\nSIP-Callid: %s\r\nTargetChannel: %s\r\nTargetUniqueid: %s\r\nTransferExten: %s\r\nTransferContext: %s\r\n",
- current.chan1->name,
- current.chan1->uniqueid,
- p->callid,
- current.chan2->name,
- current.chan2->uniqueid,
- p->refer->refer_to, p->refer->refer_to_context);
- /* Success - we have a new channel */
- ast_debug(3, "%s transfer succeeded. Telling transferer.\n", p->refer->attendedtransfer? "Attended" : "Blind");
+ /* It's important to note that the PIDF validation routine has no knowledge
+ * of what we specifically want in this instance. A valid PIDF document could
+ * have no tuples, or it could have tuples whose status element has no basic
+ * element contained within. While not violating the PIDF spec, these are
+ * insufficient for our needs in this situation
+ */
+ presence_node = ast_xml_get_root(pidf_doc);
+ if (!(presence_children = ast_xml_node_get_children(presence_node))) {
+ ast_log(LOG_WARNING, "No tuples within presence element.\n");
+ res = -1;
+ goto cc_publish_cleanup;
+ }
- while (ast_channel_trylock(current.chan1)) {
- sip_pvt_unlock(p);
- sched_yield();
- sip_pvt_lock(p);
- }
+ if (!(tuple_node = ast_xml_find_element(presence_children, "tuple", NULL, NULL))) {
+ ast_log(LOG_NOTICE, "Couldn't find tuple node?\n");
+ res = -1;
+ goto cc_publish_cleanup;
+ }
- /* XXX - what to we put in CEL 'extra' for attended transfers to external systems? NULL for now */
- ast_cel_report_event(current.chan1, p->refer->attendedtransfer? AST_CEL_ATTENDEDTRANSFER : AST_CEL_BLINDTRANSFER, NULL, p->refer->attendedtransfer ? NULL : p->refer->refer_to, current.chan2);
- ast_channel_unlock(current.chan1);
+ /* We already made sure that the tuple has a status node when we validated the PIDF
+ * document earlier. So there's no need to enclose this operation in an if statement.
+ */
+ tuple_children = ast_xml_node_get_children(tuple_node);
+ status_node = ast_xml_find_element(tuple_children, "status", NULL, NULL);
- transmit_notify_with_sipfrag(p, seqno, "200 Ok", TRUE);
- if (p->refer->localtransfer)
- p->refer->status = REFER_200OK;
- if (p->owner)
- p->owner->hangupcause = AST_CAUSE_NORMAL_CLEARING;
- append_history(p, "Xfer", "Refer succeeded.");
- ast_clear_flag(&p->flags[0], SIP_GOTREFER);
- /* Do not hangup call, the other side do that when we say 200 OK */
- /* We could possibly implement a timer here, auto congestion */
- res = 0;
- } else {
- ast_clear_flag(&p->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Don't delay hangup */
- ast_debug(3, "%s transfer failed. Resuming original call.\n", p->refer->attendedtransfer? "Attended" : "Blind");
- append_history(p, "Xfer", "Refer failed.");
- /* Failure of some kind */
- p->refer->status = REFER_FAILED;
- transmit_notify_with_sipfrag(p, seqno, "503 Service Unavailable", TRUE);
- ast_clear_flag(&p->flags[0], SIP_GOTREFER);
+ if (!(status_children = ast_xml_node_get_children(status_node))) {
+ ast_log(LOG_WARNING, "No basic elements within status element.\n");
res = -1;
+ goto cc_publish_cleanup;
}
- ast_channel_unref(current.chan1);
+ if (!(basic_node = ast_xml_find_element(status_children, "basic", NULL, NULL))) {
+ ast_log(LOG_WARNING, "Couldn't find basic node?\n");
+ res = -1;
+ goto cc_publish_cleanup;
+ }
+
+ basic_status = ast_xml_get_text(basic_node);
+
+ if (ast_strlen_zero(basic_status)) {
+ ast_log(LOG_NOTICE, "NOthing in basic node?\n");
+ res = -1;
+ goto cc_publish_cleanup;
+ }
+
+ if (!strcmp(basic_status, "open")) {
+ agent_pvt->is_available = TRUE;
+ ast_cc_agent_caller_available(agent->core_id, "Received PUBLISH stating SIP caller %s is available",
+ agent->device_name);
+ } else if (!strcmp(basic_status, "closed")) {
+ agent_pvt->is_available = FALSE;
+ ast_cc_agent_caller_busy(agent->core_id, "Received PUBLISH stating SIP caller %s is busy",
+ agent->device_name);
+ } else {
+ ast_log(LOG_NOTICE, "Invalid content in basic element: %s\n", basic_status);
+ }
+cc_publish_cleanup:
+ if (basic_status) {
+ ast_xml_free_text(basic_status);
+ }
+ if (pidf_doc) {
+ ast_xml_close(pidf_doc);
+ }
+ ao2_ref(agent, -1);
+ if (res) {
+ transmit_response(pvt, "400 Bad Request", req);
+ }
return res;
}
-/*! \brief Handle incoming CANCEL request */
-static int handle_request_cancel(struct sip_pvt *p, struct sip_request *req)
+#endif /* HAVE_LIBXML2 */
+
+static int handle_sip_publish_initial(struct sip_pvt *p, struct sip_request *req, struct event_state_compositor *esc, const int expires)
{
-
- check_via(p, req);
- sip_alreadygone(p);
+ struct sip_esc_entry *esc_entry = create_esc_entry(esc, req, expires);
+ int res = 0;
- /* At this point, we could have cancelled the invite at the same time
- as the other side sends a CANCEL. Our final reply with error code
- might not have been received by the other side before the CANCEL
- was sent, so let's just give up retransmissions and waiting for
- ACK on our error code. The call is hanging up any way. */
- if (p->invitestate == INV_TERMINATED)
- __sip_pretend_ack(p);
- else
- p->invitestate = INV_CANCELLED;
-
- if (p->owner && p->owner->_state == AST_STATE_UP) {
- /* This call is up, cancel is ignored, we need a bye */
- transmit_response(p, "200 OK", req);
- ast_debug(1, "Got CANCEL on an answered call. Ignoring... \n");
- return 0;
+ if (!esc_entry) {
+ transmit_response(p, "503 Internal Server Failure", req);
+ return -1;
+ }
+
+ if (esc->callbacks->initial_handler) {
+ res = esc->callbacks->initial_handler(p, req, esc, esc_entry);
+ }
+
+ if (!res) {
+ transmit_response_with_sip_etag(p, "200 OK", req, esc_entry, 0);
+ }
+
+ ao2_ref(esc_entry, -1);
+ return res;
+}
+
+static int handle_sip_publish_refresh(struct sip_pvt *p, struct sip_request *req, struct event_state_compositor *esc, const char * const etag, const int expires)
+{
+ struct sip_esc_entry *esc_entry = get_esc_entry(etag, esc);
+ int expires_ms = expires * 1000;
+ int res = 0;
+
+ if (!esc_entry) {
+ transmit_response(p, "412 Conditional Request Failed", req);
+ return -1;
}
- if (ast_test_flag(&p->flags[0], SIP_INC_COUNT) || ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD))
- update_call_counter(p, DEC_CALL_LIMIT);
+ AST_SCHED_REPLACE_UNREF(esc_entry->sched_id, sched, expires_ms, publish_expire, esc_entry,
+ ao2_ref(_data, -1),
+ ao2_ref(esc_entry, -1),
+ ao2_ref(esc_entry, +1));
- stop_media_flows(p); /* Immediately stop RTP, VRTP and UDPTL as applicable */
- if (p->owner) {
- ast_set_hangupsource(p->owner, p->owner->name, 0);
- ast_queue_hangup(p->owner);
+ if (esc->callbacks->refresh_handler) {
+ res = esc->callbacks->refresh_handler(p, req, esc, esc_entry);
}
- else
- sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
- if (p->initreq.len > 0) {
- struct sip_pkt *pkt, *prev_pkt;
- /* If the CANCEL we are receiving is a retransmission, and we already have scheduled
- * a reliable 487, then we don't want to schedule another one on top of the previous
- * one.
- *
- * As odd as this may sound, we can't rely on the previously-transmitted "reliable"
- * response in this situation. What if we've sent all of our reliable responses
- * already and now all of a sudden, we get this second CANCEL?
- *
- * The only way to do this correctly is to cancel our previously-scheduled reliably-
- * transmitted response and send a new one in its place.
- */
- for (pkt = p->packets, prev_pkt = NULL; pkt; prev_pkt = pkt, pkt = pkt->next) {
- if (pkt->seqno == p->lastinvite && pkt->response_code == 487) {
- AST_SCHED_DEL(sched, pkt->retransid);
- UNLINK(pkt, p->packets, prev_pkt);
- ast_free(pkt);
- break;
- }
- }
- transmit_response_reliable(p, "487 Request Terminated", &p->initreq);
- transmit_response(p, "200 OK", req);
- return 1;
- } else {
- transmit_response(p, "481 Call Leg Does Not Exist", req);
- return 0;
+
+ if (!res) {
+ transmit_response_with_sip_etag(p, "200 OK", req, esc_entry, 1);
}
+
+ ao2_ref(esc_entry, -1);
+ return res;
}
-/*! \brief Handle incoming BYE request */
-static int handle_request_bye(struct sip_pvt *p, struct sip_request *req)
+static int handle_sip_publish_modify(struct sip_pvt *p, struct sip_request *req, struct event_state_compositor *esc, const char * const etag, const int expires)
{
- struct ast_channel *c=NULL;
- int res;
- struct ast_channel *bridged_to;
-
- /* If we have an INCOMING invite that we haven't answered, terminate that transaction */
- if (p->pendinginvite && !ast_test_flag(&p->flags[0], SIP_OUTGOING) && !req->ignore) {
- transmit_response_reliable(p, "487 Request Terminated", &p->initreq);
- }
-
- __sip_pretend_ack(p);
+ struct sip_esc_entry *esc_entry = get_esc_entry(etag, esc);
+ int expires_ms = expires * 1000;
+ int res = 0;
- p->invitestate = INV_TERMINATED;
+ if (!esc_entry) {
+ transmit_response(p, "412 Conditional Request Failed", req);
+ return -1;
+ }
- copy_request(&p->initreq, req);
- if (sipdebug)
- ast_debug(1, "Initializing initreq for method %s - callid %s\n", sip_methods[req->method].text, p->callid);
- check_via(p, req);
- sip_alreadygone(p);
+ AST_SCHED_REPLACE_UNREF(esc_entry->sched_id, sched, expires_ms, publish_expire, esc_entry,
+ ao2_ref(_data, -1),
+ ao2_ref(esc_entry, -1),
+ ao2_ref(esc_entry, +1));
- /* Get RTCP quality before end of call */
- if (p->do_history || p->owner) {
- char quality_buf[AST_MAX_USER_FIELD], *quality;
- struct ast_channel *bridge = p->owner ? ast_bridged_channel(p->owner) : NULL;
+ if (esc->callbacks->modify_handler) {
+ res = esc->callbacks->modify_handler(p, req, esc, esc_entry);
+ }
- /* We need to get the lock on bridge because ast_rtp_instance_set_stats_vars will attempt
- * to lock the bridge. This may get hairy...
- */
- while (bridge && ast_channel_trylock(bridge)) {
- ast_channel_unlock(p->owner);
- do {
- /* Can't use DEADLOCK_AVOIDANCE since p is an ao2 object */
- sip_pvt_unlock(p);
- usleep(1);
- sip_pvt_lock(p);
- } while (p->owner && ast_channel_trylock(p->owner));
- bridge = p->owner ? ast_bridged_channel(p->owner) : NULL;
- }
+ if (!res) {
+ transmit_response_with_sip_etag(p, "200 OK", req, esc_entry, 1);
+ }
+ ao2_ref(esc_entry, -1);
+ return res;
+}
- if (p->rtp && (quality = ast_rtp_instance_get_quality(p->rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality_buf, sizeof(quality_buf)))) {
- if (p->do_history) {
- append_history(p, "RTCPaudio", "Quality:%s", quality);
+static int handle_sip_publish_remove(struct sip_pvt *p, struct sip_request *req, struct event_state_compositor *esc, const char * const etag)
+{
+ struct sip_esc_entry *esc_entry = get_esc_entry(etag, esc);
+ int res = 0;
- if ((quality = ast_rtp_instance_get_quality(p->rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_JITTER, quality_buf, sizeof(quality_buf)))) {
- append_history(p, "RTCPaudioJitter", "Quality:%s", quality);
- }
- if ((quality = ast_rtp_instance_get_quality(p->rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_LOSS, quality_buf, sizeof(quality_buf)))) {
- append_history(p, "RTCPaudioLoss", "Quality:%s", quality);
- }
- if ((quality = ast_rtp_instance_get_quality(p->rtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT, quality_buf, sizeof(quality_buf)))) {
- append_history(p, "RTCPaudioRTT", "Quality:%s", quality);
- }
- }
+ if (!esc_entry) {
+ transmit_response(p, "412 Conditional Request Failed", req);
+ return -1;
+ }
- if (p->owner) {
- ast_rtp_instance_set_stats_vars(p->owner, p->rtp);
- }
+ AST_SCHED_DEL(sched, esc_entry->sched_id);
+ /* Scheduler's ref of the esc_entry */
+ ao2_ref(esc_entry, -1);
- }
+ if (esc->callbacks->remove_handler) {
+ res = esc->callbacks->remove_handler(p, req, esc, esc_entry);
+ }
- if (bridge) {
- struct sip_pvt *q = bridge->tech_pvt;
+ if (!res) {
+ transmit_response_with_sip_etag(p, "200 OK", req, esc_entry, 1);
+ }
- if (IS_SIP_TECH(bridge->tech) && q && q->rtp) {
- ast_rtp_instance_set_stats_vars(bridge, q->rtp);
- }
- ast_channel_unlock(bridge);
- }
+ /* Ref from finding the esc_entry earlier in function */
+ ao2_unlink(esc->compositor, esc_entry);
+ ao2_ref(esc_entry, -1);
+ return res;
+}
- if (p->vrtp && (quality = ast_rtp_instance_get_quality(p->vrtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality_buf, sizeof(quality_buf)))) {
- if (p->do_history) {
- append_history(p, "RTCPvideo", "Quality:%s", quality);
- }
- if (p->owner) {
- pbx_builtin_setvar_helper(p->owner, "RTPVIDEOQOS", quality);
- }
- }
- if (p->trtp && (quality = ast_rtp_instance_get_quality(p->trtp, AST_RTP_INSTANCE_STAT_FIELD_QUALITY, quality_buf, sizeof(quality_buf)))) {
- if (p->do_history) {
- append_history(p, "RTCPtext", "Quality:%s", quality);
- }
- if (p->owner) {
- pbx_builtin_setvar_helper(p->owner, "RTPTEXTQOS", quality);
- }
- }
+static int handle_request_publish(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, const int seqno, const char *uri)
+{
+ const char *etag = get_header(req, "SIP-If-Match");
+ const char *event = get_header(req, "Event");
+ struct event_state_compositor *esc;
+ enum sip_publish_type publish_type;
+ const char *expires_str = get_header(req, "Expires");
+ int expires_int;
+ int auth_result;
+ int handler_result = -1;
+
+ if (ast_strlen_zero(event)) {
+ transmit_response(p, "489 Bad Event", req);
+ return -1;
}
- stop_media_flows(p); /* Immediately stop RTP, VRTP and UDPTL as applicable */
- stop_session_timer(p); /* Stop Session-Timer */
+ if (!(esc = get_esc(event))) {
+ transmit_response(p, "489 Bad Event", req);
+ return -1;
+ }
- if (!ast_strlen_zero(get_header(req, "Also"))) {
- ast_log(LOG_NOTICE, "Client '%s' using deprecated BYE/Also transfer method. Ask vendor to support REFER instead\n",
- ast_inet_ntoa(p->recv.sin_addr));
- if (ast_strlen_zero(p->context))
- ast_string_field_set(p, context, sip_cfg.default_context);
- res = get_also_info(p, req);
- if (!res) {
- c = p->owner;
- if (c) {
- bridged_to = ast_bridged_channel(c);
- if (bridged_to) {
- /* Don't actually hangup here... */
- ast_queue_control(c, AST_CONTROL_UNHOLD);
- ast_channel_unlock(c); /* async_goto can do a masquerade, no locks can be held during a masq */
- ast_async_goto(bridged_to, p->context, p->refer->refer_to, 1);
- ast_channel_lock(c);
- } else
- ast_queue_hangup(p->owner);
- }
+ auth_result = check_user(p, req, SIP_PUBLISH, uri, XMIT_RELIABLE, sin);
+ if (auth_result == AUTH_CHALLENGE_SENT) {
+ p->lastinvite = seqno;
+ return 0;
+ } else if (auth_result < 0) {
+ if (auth_result == AUTH_FAKE_AUTH) {
+ ast_log(LOG_NOTICE, "Sending fake auth rejection for device %s\n", get_header(req, "From"));
+ transmit_fake_auth_response(p, SIP_INVITE, req, XMIT_RELIABLE);
} else {
- ast_log(LOG_WARNING, "Invalid transfer information from '%s'\n", ast_inet_ntoa(p->recv.sin_addr));
- if (p->owner)
- ast_queue_hangup_with_cause(p->owner, AST_CAUSE_PROTOCOL_ERROR);
+ ast_log(LOG_NOTICE, "Failed to authenticate device %s\n", get_header(req, "From"));
+ transmit_response_reliable(p, "403 Forbidden", req);
}
- } else if (p->owner) {
- ast_set_hangupsource(p->owner, p->owner->name, 0);
- ast_queue_hangup(p->owner);
- ast_debug(3, "Received bye, issuing owner hangup\n");
- } else {
sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
- ast_debug(3, "Received bye, no owner, selfdestruct soon.\n");
+ ast_string_field_set(p, theirtag, NULL);
+ return 0;
+ } else if (auth_result == AUTH_SUCCESSFUL && p->lastinvite) {
+ /* We need to stop retransmitting the 401 */
+ __sip_ack(p, p->lastinvite, 1, 0);
}
- ast_clear_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
- transmit_response(p, "200 OK", req);
- return 1;
-}
+ publish_type = determine_sip_publish_type(req, event, etag, expires_str, &expires_int);
-/*! \brief Handle incoming MESSAGE request */
-static int handle_request_message(struct sip_pvt *p, struct sip_request *req)
-{
- if (!req->ignore) {
- if (req->debug)
- ast_verbose("Receiving message!\n");
- receive_message(p, req);
- } else
- transmit_response(p, "202 Accepted", req);
- return 1;
+ /* It is the responsibility of these handlers to formulate any response
+ * sent for a PUBLISH
+ */
+ switch (publish_type) {
+ case SIP_PUBLISH_UNKNOWN:
+ transmit_response(p, "400 Bad Request", req);
+ break;
+ case SIP_PUBLISH_INITIAL:
+ handler_result = handle_sip_publish_initial(p, req, esc, expires_int);
+ break;
+ case SIP_PUBLISH_REFRESH:
+ handler_result = handle_sip_publish_refresh(p, req, esc, etag, expires_int);
+ break;
+ case SIP_PUBLISH_MODIFY:
+ handler_result = handle_sip_publish_modify(p, req, esc, etag, expires_int);
+ break;
+ case SIP_PUBLISH_REMOVE:
+ handler_result = handle_sip_publish_remove(p, req, esc, etag);
+ break;
+ default:
+ transmit_response(p, "400 Impossible Condition", req);
+ break;
+ }
+
+ return handler_result;
}
static void add_peer_mwi_subs(struct sip_peer *peer)
}
}
+static int handle_cc_subscribe(struct sip_pvt *p, struct sip_request *req)
+{
+ const char *uri = REQ_OFFSET_TO_STR(req, rlPart2);
+ char *param_separator;
+ struct ast_cc_agent *agent;
+ struct sip_cc_agent_pvt *agent_pvt;
+ const char *expires_str = get_header(req, "Expires");
+ int expires = -1; /* Just need it to be non-zero */
+
+ if (!ast_strlen_zero(expires_str)) {
+ sscanf(expires_str, "%d", &expires);
+ }
+
+ if ((param_separator = strchr(uri, ';'))) {
+ *param_separator = '\0';
+ }
+
+ if (!(agent = find_sip_cc_agent_by_subscribe_uri(uri))) {
+ if (!expires) {
+ /* Typically, if a 0 Expires reaches us and we can't find
+ * the corresponding agent, it means that the CC transaction
+ * has completed and so the calling side is just trying to
+ * clean up its subscription. We'll just respond with a
+ * 200 OK and be done with it
+ */
+ transmit_response(p, "200 OK", req);
+ return 0;
+ }
+ ast_log(LOG_WARNING, "Invalid URI '%s' in CC subscribe\n", uri);
+ transmit_response(p, "404 Not Found", req);
+ return -1;
+ }
+
+ agent_pvt = agent->private_data;
+
+ if (!expires) {
+ /* We got sent a SUBSCRIBE and found an agent. This means that CC
+ * is being canceled.
+ */
+ ast_cc_failed(agent->core_id, "CC is being canceled by %s", agent->device_name);
+ transmit_response(p, "200 OK", req);
+ ao2_ref(agent, -1);
+ return 0;
+ }
+
+ agent_pvt->subscribe_pvt = dialog_ref(p, "SIP CC agent gains reference to subscription dialog");
+ ast_cc_agent_accept_request(agent->core_id, "SIP caller %s has requested CC via SUBSCRIBE",
+ agent->device_name);
+ p->subscribed = CALL_COMPLETION;
+
+ /* We don't send a response here. That is done in the agent's ack callback or in the
+ * agent destructor, should a failure occur before we have responded
+ */
+ ao2_ref(agent, -1);
+ return 0;
+}
+
/*! \brief Handle incoming SUBSCRIBE request */
static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, int seqno, const char *e)
{
return 0;
}
- if (strcmp(event, "message-summary")) {
+ if (strcmp(event, "message-summary") && strcmp(event, "call-completion")) {
/* Get destination right away */
- gotdest = get_destination(p, NULL);
+ gotdest = get_destination(p, NULL, NULL);
}
/* Get full contact header - this needs to be used as a request URI in NOTIFY's */
unref_peer(p->relatedpeer, "Unref previously stored relatedpeer ptr");
p->relatedpeer = ref_peer(authpeer, "setting dialog's relatedpeer pointer"); /* already refcounted...Link from pvt to peer UH- should this be dialog_ref()? */
/* Do not release authpeer here */
+ } else if (!strcmp(event, "call-completion")) {
+ handle_cc_subscribe(p, req);
} else { /* At this point, Asterisk does not understand the specified event */
transmit_response(p, "489 Bad Event", req);
ast_debug(2, "Received SIP subscribe for unknown event package: %s\n", event);
}
/* Add subscription for extension state from the PBX core */
- if (p->subscribed != MWI_NOTIFICATION && !resubscribe) {
+ if (p->subscribed != MWI_NOTIFICATION && p->subscribed != CALL_COMPLETION && !resubscribe) {
if (p->stateid > -1) {
ast_extension_state_del(p->stateid, cb_extensionstate);
/* we need to dec the refcount, now that the extensionstate is removed */
p->expiry = min_expiry;
if (sipdebug) {
- if (p->subscribed == MWI_NOTIFICATION && p->relatedpeer)
+ if (p->subscribed == MWI_NOTIFICATION && p->relatedpeer) {
ast_debug(2, "Adding subscription for mailbox notification - peer %s\n", p->relatedpeer->name);
- else
+ } else if (p->subscribed == CALL_COMPLETION) {
+ ast_debug(2, "Adding CC subscription for peer %s\n", p->username);
+ } else {
ast_debug(2, "Adding subscription for extension %s context %s for peer %s\n", p->exten, p->context, p->username);
+ }
}
if (p->autokillid > -1 && sip_cancel_destroy(p)) /* Remove subscription expiry for renewals */
ast_log(LOG_WARNING, "Unable to cancel SIP destruction. Expect bad things.\n");
sip_send_mwi_to_peer(p->relatedpeer, NULL, 0);
ao2_unlock(p->relatedpeer);
}
- } else {
+ } else if (p->subscribed != CALL_COMPLETION) {
struct sip_pvt *p_old;
if ((firststate = ast_extension_state(NULL, p->context, p->exten)) < 0) {
}
}
- if (!e && (p->method == SIP_INVITE || p->method == SIP_SUBSCRIBE || p->method == SIP_REGISTER || p->method == SIP_NOTIFY)) {
+ if (!e && (p->method == SIP_INVITE || p->method == SIP_SUBSCRIBE || p->method == SIP_REGISTER || p->method == SIP_NOTIFY || p->method == SIP_PUBLISH)) {
transmit_response(p, "400 Bad request", req);
sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
return -1;
case SIP_MESSAGE:
res = handle_request_message(p, req);
break;
+ case SIP_PUBLISH:
+ res = handle_request_publish(p, req, sin, seqno, e);
+ break;
case SIP_SUBSCRIBE:
res = handle_request_subscribe(p, req, sin, seqno, e);
break;
return;
}
p->stimer->st_interval = minse;
- transmit_invite(p, SIP_INVITE, 1, 2);
+ transmit_invite(p, SIP_INVITE, 1, 2, NULL);
}
ast_set_flag(&p->flags[0], SIP_OUTGOING);
#ifdef VOCAL_DATA_HACK
ast_copy_string(p->username, "__VOCAL_DATA_SHOULD_READ_THE_SIP_SPEC__", sizeof(p->username));
- xmitres = transmit_invite(p, SIP_INVITE, 0, 2); /* sinks the p refcount */
+ xmitres = transmit_invite(p, SIP_INVITE, 0, 2, NULL); /* sinks the p refcount */
#else
- xmitres = transmit_invite(p, SIP_OPTIONS, 0, 2); /* sinks the p refcount */
+ xmitres = transmit_invite(p, SIP_OPTIONS, 0, 2, NULL); /* sinks the p refcount */
#endif
peer->ps = ast_tvnow();
if (xmitres == XMIT_ERROR) {
char *md5secret = NULL;
char *authname = NULL;
char *trans = NULL;
+ char dialstring[256];
char *remote_address;
enum sip_transport transport = 0;
struct sockaddr_in remote_address_sin = { .sin_family = AF_INET };
p->outgoing_call = TRUE;
+ snprintf(dialstring, sizeof(dialstring), "%s/%s", type, dest);
+ ast_string_field_set(p, dialstring, dialstring);
+
if (!(p->options = ast_calloc(1, sizeof(*p->options)))) {
dialog_unlink_all(p, TRUE, TRUE);
dialog_unref(p, "unref dialog p from mem fail");
return NULL;
}
+ if (!(peer->cc_params = ast_cc_config_params_init())) {
+ ao2_t_ref(peer, -1, "failed to allocate cc_params for peer");
+ return NULL;
+ }
+
if (realtime && !ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS)) {
ast_atomic_fetchadd_int(&rpeerobjs, 1);
ast_debug(3, "-REALTIME- peer built. Name: %s. Peer objects: %d\n", name, rpeerobjs);
if (peer->busy_level < 0) {
peer->busy_level = 0;
}
+ } else if (ast_cc_is_config_param(v->name)) {
+ ast_cc_set_param(peer->cc_params, v->name, v->value);
}
}
+ if (!can_parse_xml && (ast_get_cc_agent_policy(peer->cc_params) == AST_CC_AGENT_NATIVE)) {
+ ast_log(LOG_WARNING, "Peer %s has a cc_agent_policy of 'native' but required libxml2 dependency is not installed. Changing policy to 'never'\n", peer->name);
+ ast_set_cc_agent_policy(peer->cc_params, AST_CC_AGENT_NEVER);
+ }
+
/* Note that Timer B is dependent upon T1 and MUST NOT be lower
* than T1 * 64, according to RFC 3261, Section 17.1.1.2 */
if (peer->timer_b < peer->timer_t1 * 64) {
return 0;
}
+static int sip_is_xml_parsable(void)
+{
+#ifdef HAVE_LIBXML2
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
+
/*! \brief Send a poke to all known peers */
static void sip_poke_all_peers(void)
{
sip_reloadreason = CHANNEL_MODULE_LOAD;
+ can_parse_xml = sip_is_xml_parsable();
if(reload_config(sip_reloadreason)) /* Load the configuration from sip.conf */
return AST_MODULE_LOAD_DECLINE;
sip_poke_all_peers();
sip_send_all_registers();
sip_send_all_mwi_subscriptions();
+ initialize_escs();
+ if (sip_epa_register(&cc_epa_static_data)) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ if (can_parse_xml) {
+ /* SIP CC agents require the ability to parse XML PIDF bodies
+ * in incoming PUBLISH requests
+ */
+ if (ast_cc_agent_register(&sip_cc_agent_callbacks)) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ }
+ if (ast_cc_monitor_register(&sip_cc_monitor_callbacks)) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ if (!(sip_monitor_instances = ao2_container_alloc(37, sip_monitor_instance_hash_fn, sip_monitor_instance_cmp_fn))) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
/* And start the monitor for the first time */
restart_monitor();
clear_realm_authentication(authl);
+ destroy_escs();
if (default_tls_cfg.certfile)
ast_free(default_tls_cfg.certfile);
ast_context_destroy(con, "SIP");
ast_unload_realtime("sipregs");
ast_unload_realtime("sippeers");
+ ast_cc_monitor_unregister(&sip_cc_monitor_callbacks);
+ ast_cc_agent_unregister(&sip_cc_agent_callbacks);
sip_unregister_tests();
--- /dev/null
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2010, Digium, Inc.
+ *
+ * Mark Michelson <mmichelson@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ * \brief Call Completion Supplementary Services implementation
+ * \author Mark Michelson <mmichelson@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/astobj2.h"
+#include "asterisk/strings.h"
+#include "asterisk/ccss.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/utils.h"
+#include "asterisk/taskprocessor.h"
+#include "asterisk/event.h"
+#include "asterisk/module.h"
+#include "asterisk/app.h"
+#include "asterisk/cli.h"
+#include "asterisk/manager.h"
+#include "asterisk/causes.h"
+
+/*** DOCUMENTATION
+ <application name="CallCompletionRequest" language="en_US">
+ <synopsis>
+ Request call completion service for previous call
+ </synopsis>
+ <syntax />
+ <description>
+ <para>Request call completion service for a previously failed
+ call attempt.</para>
+ </description>
+ </application>
+ <application name="CallCompletionCancel" language="en_US">
+ <synopsis>
+ Cancel call completion service
+ </synopsis>
+ <syntax />
+ <description>
+ <para>Cancel a Call Completion Request.</para>
+ </description>
+ </application>
+ ***/
+
+/* These are some file-scoped variables. It would be
+ * nice to define them closer to their first usage, but since
+ * they are used in many places throughout the file, defining
+ * them here at the top is easiest.
+ */
+
+/*!
+ * The sched_thread ID used for all generic CC timeouts
+ */
+static struct ast_sched_thread *cc_sched_thread;
+/*!
+ * Counter used to create core IDs for CC calls. Each new
+ * core ID is created by atomically adding 1 to the core_id_counter
+ */
+static int core_id_counter;
+/*!
+ * Taskprocessor from which all CC agent and monitor callbacks
+ * are called.
+ */
+static struct ast_taskprocessor *cc_core_taskprocessor;
+/*!
+ * Name printed on all CC log messages.
+ */
+static const char *CC_LOGGER_LEVEL_NAME = "CC";
+/*!
+ * Logger level registered by the CC core.
+ */
+static int cc_logger_level;
+/*!
+ * Parsed configuration value for cc_max_requests
+ */
+static unsigned int global_cc_max_requests;
+/*!
+ * The current number of CC requests in the system
+ */
+static int cc_request_count;
+
+#define cc_ref(obj, debug) ({ao2_t_ref((obj), +1, (debug)); (obj);})
+#define cc_unref(obj, debug) ({ao2_t_ref((obj), -1, (debug)); NULL;})
+
+/*!
+ * \since 1.8
+ * \internal
+ * \brief A structure for holding the configuration parameters
+ * relating to CCSS
+ */
+struct ast_cc_config_params {
+ enum ast_cc_agent_policies cc_agent_policy;
+ enum ast_cc_monitor_policies cc_monitor_policy;
+ unsigned int cc_offer_timer;
+ unsigned int ccnr_available_timer;
+ unsigned int ccbs_available_timer;
+ unsigned int cc_recall_timer;
+ unsigned int cc_max_agents;
+ unsigned int cc_max_monitors;
+ char cc_callback_macro[AST_MAX_EXTENSION];
+ char cc_agent_dialstring[AST_MAX_EXTENSION];
+};
+
+/*!
+ * \since 1.8
+ * \brief The states used in the CCSS core state machine
+ *
+ * For more information, see doc/CCSS_architecture.pdf
+ */
+enum cc_state {
+ /*! Entered when it is determined that CCSS may be used for the call */
+ CC_AVAILABLE,
+ /*! Entered when a CCSS agent has offered CCSS to a caller */
+ CC_CALLER_OFFERED,
+ /*! Entered when a CCSS agent confirms that a caller has
+ * requested CCSS */
+ CC_CALLER_REQUESTED,
+ /*! Entered when a CCSS monitor confirms acknowledgment of an
+ * outbound CCSS request */
+ CC_ACTIVE,
+ /*! Entered when a CCSS monitor alerts the core that the called party
+ * has become available */
+ CC_CALLEE_READY,
+ /*! Entered when a CCSS agent alerts the core that the calling party
+ * may not be recalled because he is unavailable
+ */
+ CC_CALLER_BUSY,
+ /*! Entered when a CCSS agent alerts the core that the calling party
+ * is attempting to recall the called party
+ */
+ CC_RECALLING,
+ /*! Entered when an application alerts the core that the calling party's
+ * recall attempt has had a call progress response indicated
+ */
+ CC_COMPLETE,
+ /*! Entered any time that something goes wrong during the process, thus
+ * resulting in the failure of the attempted CCSS transaction. Note also
+ * that cancellations of CC are treated as failures.
+ */
+ CC_FAILED,
+};
+
+/*!
+ * \brief The payload for an AST_CONTROL_CC frame
+ *
+ * \details
+ * This contains all the necessary data regarding
+ * a called device so that the CC core will be able
+ * to allocate the proper monitoring resources.
+ */
+struct cc_control_payload {
+ /*!
+ * \brief The type of monitor to allocate.
+ *
+ * \details
+ * The type of monitor to allocate. This is a string which corresponds
+ * to a set of monitor callbacks registered. Examples include "generic"
+ * and "SIP"
+ *
+ * \note This really should be an array of characters in case this payload
+ * is sent accross an IAX2 link. However, this would not make too much sense
+ * given this type may not be recognized by the other end.
+ * Protection may be necessary to prevent it from being transmitted.
+ *
+ * In addition the following other problems are also possible:
+ * 1) Endian issues with the integers/enums stored in the config_params.
+ * 2) Alignment padding issues for the element types.
+ */
+ const char *monitor_type;
+ /*!
+ * \brief Private data allocated by the callee
+ *
+ * \details
+ * All channel drivers that monitor endpoints will need to allocate
+ * data that is not usable by the CC core. In most cases, some or all
+ * of this data is allocated at the time that the channel driver offers
+ * CC to the caller. There are many opportunities for failures to occur
+ * between when a channel driver offers CC and when a monitor is actually
+ * allocated to watch the endpoint. For this reason, the channel driver
+ * must give the core a pointer to the private data that was allocated so
+ * that the core can call back into the channel driver to destroy it if
+ * a failure occurs. If no private data has been allocated at the time that
+ * CC is offered, then it is perfectly acceptable to pass NULL for this
+ * field.
+ */
+ void *private_data;
+ /*!
+ * \brief Service offered by the endpoint
+ *
+ * \details
+ * This indicates the type of call completion service offered by the
+ * endpoint. This data is not crucial to the machinations of the CC core,
+ * but it is helpful for debugging purposes.
+ */
+ enum ast_cc_service_type service;
+ /*!
+ * \brief Configuration parameters used by this endpoint
+ *
+ * \details
+ * Each time an endpoint offers call completion, it must provide its call
+ * completion configuration parameters. This is because settings may be different
+ * depending on the circumstances.
+ */
+ struct ast_cc_config_params config_params;
+ /*!
+ * \brief ID of parent extension
+ *
+ * \details
+ * This is the only datum that the CC core derives on its own and is not
+ * provided by the offerer of CC. This provides the core with information on
+ * which extension monitor is the most immediate parent of this device.
+ */
+ int parent_interface_id;
+ /*!
+ * \brief Name of device to be monitored
+ *
+ * \details
+ * The device name by which this monitored endpoint will be referred in the
+ * CC core. It is highly recommended that this device name is derived by using
+ * the function ast_channel_get_device_name.
+ */
+ char device_name[AST_CHANNEL_NAME];
+ /*!
+ * \brief Recall dialstring
+ *
+ * \details
+ * Certain channel drivers (DAHDI in particular) will require that a special
+ * dialstring be used to indicate that the outgoing call is to interpreted as
+ * a CC recall. If the channel driver has such a requirement, then this is
+ * where that special recall dialstring is placed. If no special dialstring
+ * is to be used, then the channel driver must provide the original dialstring
+ * used to call this endpoint.
+ */
+ char dialstring[AST_CHANNEL_NAME];
+};
+
+/*!
+ * \brief The "tree" of interfaces that is dialed.
+ *
+ * \details
+ * Though this is a linked list, it is logically treated
+ * as a tree of monitors. Each monitor has an id and a parent_id
+ * associated with it. The id is a unique ID for that monitor, and
+ * the parent_id is the unique ID of the monitor's parent in the
+ * tree. The tree is structured such that all of a parent's children
+ * will appear after the parent in the tree. However, it cannot be
+ * guaranteed exactly where after the parent the children are.
+ *
+ * The tree is reference counted since several threads may need
+ * to use it, and it may last beyond the lifetime of a single
+ * thread.
+ */
+AST_LIST_HEAD(cc_monitor_tree, ast_cc_monitor);
+
+static const int CC_CORE_INSTANCES_BUCKETS = 17;
+static struct ao2_container *cc_core_instances;
+
+struct cc_core_instance {
+ /*!
+ * Unique identifier for this instance of the CC core.
+ */
+ int core_id;
+ /*!
+ * The current state for this instance of the CC core.
+ */
+ enum cc_state current_state;
+ /*!
+ * The CC agent in use for this call
+ */
+ struct ast_cc_agent *agent;
+ /*!
+ * Reference to the monitor tree formed during the initial call
+ */
+ struct cc_monitor_tree *monitors;
+};
+
+/*!
+ * \internal
+ * \brief Request that the core change states
+ * \param state The state to which we wish to change
+ * \param core_id The unique identifier for this instance of the CCSS core state machine
+ * \param debug Optional message explaining the reason for the state change
+ * \param ap varargs list
+ * \retval 0 State change successfully queued
+ * \retval -1 Unable to queue state change request
+ */
+static int __attribute__((format(printf, 3, 0))) cc_request_state_change(enum cc_state state, const int core_id, const char *debug, va_list ap);
+
+/*!
+ * \internal
+ * \brief create a new instance of the CC core and an agent for the calling channel
+ *
+ * This function will check to make sure that the incoming channel
+ * is allowed to request CC by making sure that the incoming channel
+ * has not exceeded its maximum number of allowed agents.
+ *
+ * Should that check pass, the core instance is created, and then the
+ * agent for the channel.
+ *
+ * \param caller_chan The incoming channel for this particular call
+ * \param called_tree A reference to the tree of called devices. The agent
+ * will gain a reference to this tree as well
+ * \param core_id The core_id that this core_instance will assume
+ * \retval NULL Failed to create the core instance either due to memory allocation
+ * errors or due to the agent count for the caller being too high
+ * \retval non-NULL A reference to the newly created cc_core_instance
+ */
+static struct cc_core_instance *cc_core_init_instance(struct ast_channel *caller_chan,
+ struct cc_monitor_tree *called_tree, const int core_id, struct cc_control_payload *cc_data);
+
+static const struct {
+ enum ast_cc_service_type service;
+ const char *service_string;
+} cc_service_to_string_map[] = {
+ {AST_CC_NONE, "NONE"},
+ {AST_CC_CCBS, "CCBS"},
+ {AST_CC_CCNR, "CCNR"},
+ {AST_CC_CCNL, "CCNL"},
+};
+
+static const struct {
+ enum cc_state state;
+ const char *state_string;
+} cc_state_to_string_map[] = {
+ {CC_AVAILABLE, "CC is available"},
+ {CC_CALLER_OFFERED, "CC offered to caller"},
+ {CC_CALLER_REQUESTED, "CC requested by caller"},
+ {CC_ACTIVE, "CC accepted by callee"},
+ {CC_CALLEE_READY, "Callee has become available"},
+ {CC_CALLER_BUSY, "Callee was ready, but caller is now unavailable"},
+ {CC_RECALLING, "Caller is attempting to recall"},
+ {CC_COMPLETE, "Recall complete"},
+ {CC_FAILED, "CC has failed"},
+};
+
+static const char *cc_state_to_string(enum cc_state state)
+{
+ return cc_state_to_string_map[state].state_string;
+}
+
+static const char *cc_service_to_string(enum ast_cc_service_type service)
+{
+ return cc_service_to_string_map[service].service_string;
+}
+
+static int cc_core_instance_hash_fn(const void *obj, const int flags)
+{
+ const struct cc_core_instance *core_instance = obj;
+ return core_instance->core_id;
+}
+
+static int cc_core_instance_cmp_fn(void *obj, void *arg, int flags)
+{
+ struct cc_core_instance *core_instance1 = obj;
+ struct cc_core_instance *core_instance2 = arg;
+
+ return core_instance1->core_id == core_instance2->core_id ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static struct cc_core_instance *find_cc_core_instance(const int core_id)
+{
+ struct cc_core_instance finder = {.core_id = core_id,};
+
+ return ao2_t_find(cc_core_instances, &finder, OBJ_POINTER, "Finding a core_instance");
+}
+
+struct cc_callback_helper {
+ ao2_callback_fn *function;
+ void *args;
+ const char *type;
+};
+
+static int cc_agent_callback_helper(void *obj, void *args, int flags)
+{
+ struct cc_core_instance *core_instance = obj;
+ struct cc_callback_helper *helper = args;
+
+ if (strcmp(core_instance->agent->callbacks->type, helper->type)) {
+ return 0;
+ }
+
+ return helper->function(core_instance->agent, helper->args, flags);
+}
+
+struct ast_cc_agent *ast_cc_agent_callback(int flags, ao2_callback_fn *function, void *args, const char * const type)
+{
+ struct cc_callback_helper helper = {.function = function, .args = args, .type = type};
+ struct cc_core_instance *core_instance;
+ if ((core_instance = ao2_t_callback(cc_core_instances, flags, cc_agent_callback_helper, &helper,
+ "Calling provided agent callback function"))) {
+ struct ast_cc_agent *agent = cc_ref(core_instance->agent, "An outside entity needs the agent");
+ cc_unref(core_instance, "agent callback done with the core_instance");
+ return agent;
+ }
+ return NULL;
+}
+
+enum match_flags {
+ /* Only match agents that have not yet
+ * made a CC request
+ */
+ MATCH_NO_REQUEST = (1 << 0),
+ /* Only match agents that have made
+ * a CC request
+ */
+ MATCH_REQUEST = (1 << 1),
+};
+
+/* ao2_callbacks for cc_core_instances */
+
+/*!
+ * \internal
+ * \brief find a core instance based on its agent
+ *
+ * The match flags tell whether we wish to find core instances
+ * that have a monitor or core instances that do not. Core instances
+ * with no monitor are core instances for which a caller has not yet
+ * requested CC. Core instances with a monitor are ones for which the
+ * caller has requested CC.
+ */
+static int match_agent(void *obj, void *arg, void *data, int flags)
+{
+ struct cc_core_instance *core_instance = obj;
+ const char *name = arg;
+ unsigned long match_flags = *(unsigned long *)data;
+ int possible_match = 0;
+
+ if ((match_flags & MATCH_NO_REQUEST) && core_instance->current_state < CC_CALLER_REQUESTED) {
+ possible_match = 1;
+ }
+
+ if ((match_flags & MATCH_REQUEST) && core_instance->current_state >= CC_CALLER_REQUESTED) {
+ possible_match = 1;
+ }
+
+ if (!possible_match) {
+ return 0;
+ }
+
+ if (!strcmp(core_instance->agent->device_name, name)) {
+ return CMP_MATCH | CMP_STOP;
+ }
+ return 0;
+}
+
+struct count_agents_cb_data {
+ int count;
+ int core_id_exception;
+};
+
+/*!
+ * \internal
+ * \brief Count the number of agents a specific interface is using
+ *
+ * We're only concerned with the number of agents that have requested
+ * CC, so we restrict our search to core instances which have a non-NULL
+ * monitor pointer
+ */
+static int count_agents_cb(void *obj, void *arg, void *data, int flags)
+{
+ struct cc_core_instance *core_instance = obj;
+ const char *name = arg;
+ struct count_agents_cb_data *cb_data = data;
+
+ if (cb_data->core_id_exception == core_instance->core_id) {
+ ast_log_dynamic_level(cc_logger_level, "Found agent with core_id %d but not counting it toward total\n", core_instance->core_id);
+ return 0;
+ }
+
+ if (core_instance->current_state >= CC_CALLER_REQUESTED && !strcmp(core_instance->agent->device_name, name)) {
+ cb_data->count++;
+ }
+ return 0;
+}
+
+static const unsigned int CC_OFFER_TIMER_DEFAULT = 20u;
+static const unsigned int CCNR_AVAILABLE_TIMER_DEFAULT = 7200u;
+static const unsigned int CCBS_AVAILABLE_TIMER_DEFAULT = 4800u;
+static const unsigned int CC_RECALL_TIMER_DEFAULT = 20u;
+static const unsigned int CC_MAX_AGENTS_DEFAULT = 5u;
+static const unsigned int CC_MAX_MONITORS_DEFAULT = 5u;
+static const unsigned int GLOBAL_CC_MAX_REQUESTS_DEFAULT = 20u;
+
+struct ast_cc_config_params *__ast_cc_config_params_init(const char *file, int line, const char *function)
+{
+#if defined(__AST_DEBUG_MALLOC)
+ struct ast_cc_config_params *params = __ast_calloc(1, sizeof(*params), file, line, function);
+#else
+ struct ast_cc_config_params *params = ast_calloc(1, sizeof(*params));
+#endif
+
+ if (!params) {
+ return NULL;
+ }
+
+ /* Yeah, I could use the get/set functions, but what's the point since
+ * I have direct access to the structure fields in this file.
+ */
+ params->cc_agent_policy = AST_CC_AGENT_NEVER;
+ params->cc_monitor_policy = AST_CC_MONITOR_NEVER;
+ params->cc_offer_timer = CC_OFFER_TIMER_DEFAULT;
+ params->ccnr_available_timer = CCNR_AVAILABLE_TIMER_DEFAULT;
+ params->ccbs_available_timer = CCBS_AVAILABLE_TIMER_DEFAULT;
+ params->cc_recall_timer = CC_RECALL_TIMER_DEFAULT;
+ params->cc_max_agents = CC_MAX_AGENTS_DEFAULT;
+ params->cc_max_monitors = CC_MAX_MONITORS_DEFAULT;
+ /* No need to set cc_callback_macro since calloc will 0 it out anyway */
+ return params;
+}
+
+void ast_cc_config_params_destroy(struct ast_cc_config_params *params)
+{
+ ast_free(params);
+}
+
+static enum ast_cc_agent_policies str_to_agent_policy(const char * const value)
+{
+ if (!strcasecmp(value, "never")) {
+ return AST_CC_AGENT_NEVER;
+ } else if (!strcasecmp(value, "native")) {
+ return AST_CC_AGENT_NATIVE;
+ } else if (!strcasecmp(value, "generic")) {
+ return AST_CC_AGENT_GENERIC;
+ } else {
+ ast_log(LOG_WARNING, "%s is an invalid value for cc_agent_policy. Switching to 'never'\n", value);
+ return AST_CC_AGENT_NEVER;
+ }
+}
+
+static enum ast_cc_monitor_policies str_to_monitor_policy(const char * const value)
+{
+ if (!strcasecmp(value, "never")) {
+ return AST_CC_MONITOR_NEVER;
+ } else if (!strcasecmp(value, "native")) {
+ return AST_CC_MONITOR_NATIVE;
+ } else if (!strcasecmp(value, "generic")) {
+ return AST_CC_MONITOR_GENERIC;
+ } else if (!strcasecmp(value, "always")) {
+ return AST_CC_MONITOR_ALWAYS;
+ } else {
+ ast_log(LOG_WARNING, "%s is an invalid value for cc_monitor_policy. Switching to 'never'\n", value);
+ return AST_CC_MONITOR_NEVER;
+ }
+}
+
+static const char *agent_policy_to_str(enum ast_cc_agent_policies policy)
+{
+ switch (policy) {
+ case AST_CC_AGENT_NEVER:
+ return "never";
+ case AST_CC_AGENT_NATIVE:
+ return "native";
+ case AST_CC_AGENT_GENERIC:
+ return "generic";
+ default:
+ /* This should never happen... */
+ return "";
+ }
+}
+
+static const char *monitor_policy_to_str(enum ast_cc_monitor_policies policy)
+{
+ switch (policy) {
+ case AST_CC_MONITOR_NEVER:
+ return "never";
+ case AST_CC_MONITOR_NATIVE:
+ return "native";
+ case AST_CC_MONITOR_GENERIC:
+ return "generic";
+ case AST_CC_MONITOR_ALWAYS:
+ return "always";
+ default:
+ /* This should never happen... */
+ return "";
+ }
+}
+int ast_cc_get_param(struct ast_cc_config_params *params, const char * const name,
+ char *buf, size_t buf_len)
+{
+ const char *value = NULL;
+ if (!strcasecmp(name, "cc_callback_macro")) {
+ value = ast_get_cc_callback_macro(params);
+ } else if (!strcasecmp(name, "cc_agent_policy")) {
+ value = agent_policy_to_str(ast_get_cc_agent_policy(params));
+ } else if (!strcasecmp(name, "cc_monitor_policy")) {
+ value = monitor_policy_to_str(ast_get_cc_monitor_policy(params));
+ } else if (!strcasecmp(name, "cc_agent_dialstring")) {
+ value = ast_get_cc_agent_dialstring(params);
+ }
+
+ if (!ast_strlen_zero(value)) {
+ ast_copy_string(buf, value, buf_len);
+ return 0;
+ }
+
+ /* The rest of these are all ints of some sort and require some
+ * snprintf-itude
+ */
+
+ if (!strcasecmp(name, "cc_offer_timer")) {
+ snprintf(buf, buf_len, "%u", ast_get_cc_offer_timer(params));
+ } else if (!strcasecmp(name, "ccnr_available_timer")) {
+ snprintf(buf, buf_len, "%u", ast_get_ccnr_available_timer(params));
+ } else if (!strcasecmp(name, "ccbs_available_timer")) {
+ snprintf(buf, buf_len, "%u", ast_get_ccbs_available_timer(params));
+ } else if (!strcasecmp(name, "cc_max_agents")) {
+ snprintf(buf, buf_len, "%u", ast_get_cc_max_agents(params));
+ } else if (!strcasecmp(name, "cc_max_monitors")) {
+ snprintf(buf, buf_len, "%u", ast_get_cc_max_monitors(params));
+ } else if (!strcasecmp(name, "cc_recall_timer")) {
+ snprintf(buf, buf_len, "%u", ast_get_cc_recall_timer(params));
+ } else {
+ ast_log(LOG_WARNING, "%s is not a valid CC parameter. Ignoring.\n", name);
+ return -1;
+ }
+
+ return 0;
+}
+
+int ast_cc_set_param(struct ast_cc_config_params *params, const char * const name,
+ const char * const value)
+{
+ unsigned int value_as_uint;
+ if (!strcasecmp(name, "cc_agent_policy")) {
+ return ast_set_cc_agent_policy(params, str_to_agent_policy(value));
+ } else if (!strcasecmp(name, "cc_monitor_policy")) {
+ return ast_set_cc_monitor_policy(params, str_to_monitor_policy(value));
+ } else if (!strcasecmp(name, "cc_agent_dialstring")) {
+ ast_set_cc_agent_dialstring(params, value);
+ } else if (!strcasecmp(name, "cc_callback_macro")) {
+ ast_set_cc_callback_macro(params, value);
+ return 0;
+ }
+
+ if (!sscanf(value, "%30u", &value_as_uint) == 1) {
+ return -1;
+ }
+
+ if (!strcasecmp(name, "cc_offer_timer")) {
+ ast_set_cc_offer_timer(params, value_as_uint);
+ } else if (!strcasecmp(name, "ccnr_available_timer")) {
+ ast_set_ccnr_available_timer(params, value_as_uint);
+ } else if (!strcasecmp(name, "ccbs_available_timer")) {
+ ast_set_ccbs_available_timer(params, value_as_uint);
+ } else if (!strcasecmp(name, "cc_max_agents")) {
+ ast_set_cc_max_agents(params, value_as_uint);
+ } else if (!strcasecmp(name, "cc_max_monitors")) {
+ ast_set_cc_max_monitors(params, value_as_uint);
+ } else if (!strcasecmp(name, "cc_recall_timer")) {
+ ast_set_cc_recall_timer(params, value_as_uint);
+ } else {
+ ast_log(LOG_WARNING, "%s is not a valid CC parameter. Ignoring.\n", name);
+ return -1;
+ }
+
+ return 0;
+}
+
+int ast_cc_is_config_param(const char * const name)
+{
+ return (!strcasecmp(name, "cc_agent_policy") ||
+ !strcasecmp(name, "cc_monitor_policy") ||
+ !strcasecmp(name, "cc_offer_timer") ||
+ !strcasecmp(name, "ccnr_available_timer") ||
+ !strcasecmp(name, "ccbs_available_timer") ||
+ !strcasecmp(name, "cc_max_agents") ||
+ !strcasecmp(name, "cc_max_monitors") ||
+ !strcasecmp(name, "cc_callback_macro") ||
+ !strcasecmp(name, "cc_agent_dialstring") ||
+ !strcasecmp(name, "cc_recall_timer"));
+}
+
+void ast_cc_copy_config_params(struct ast_cc_config_params *dest, const struct ast_cc_config_params *src)
+{
+ *dest = *src;
+}
+
+enum ast_cc_agent_policies ast_get_cc_agent_policy(struct ast_cc_config_params *config)
+{
+ return config->cc_agent_policy;
+}
+
+int ast_set_cc_agent_policy(struct ast_cc_config_params *config, enum ast_cc_agent_policies value)
+{
+ /* Screw C and its weak type checking for making me have to do this
+ * validation at runtime.
+ */
+ if (value < AST_CC_AGENT_NEVER || value > AST_CC_AGENT_GENERIC) {
+ return -1;
+ }
+ config->cc_agent_policy = value;
+ return 0;
+}
+
+enum ast_cc_monitor_policies ast_get_cc_monitor_policy(struct ast_cc_config_params *config)
+{
+ return config->cc_monitor_policy;
+}
+
+int ast_set_cc_monitor_policy(struct ast_cc_config_params *config, enum ast_cc_monitor_policies value)
+{
+ /* Screw C and its weak type checking for making me have to do this
+ * validation at runtime.
+ */
+ if (value < AST_CC_MONITOR_NEVER || value > AST_CC_MONITOR_ALWAYS) {
+ return -1;
+ }
+ config->cc_monitor_policy = value;
+ return 0;
+}
+
+unsigned int ast_get_cc_offer_timer(struct ast_cc_config_params *config)
+{
+ return config->cc_offer_timer;
+}
+
+void ast_set_cc_offer_timer(struct ast_cc_config_params *config, unsigned int value)
+{
+ /* 0 is an unreasonable value for any timer. Stick with the default */
+ if (value == 0) {
+ ast_log(LOG_WARNING, "0 is an invalid value for cc_offer_timer. Retaining value as %u\n", config->cc_offer_timer);
+ return;
+ }
+ config->cc_offer_timer = value;
+}
+
+unsigned int ast_get_ccnr_available_timer(struct ast_cc_config_params *config)
+{
+ return config->ccnr_available_timer;
+}
+
+void ast_set_ccnr_available_timer(struct ast_cc_config_params *config, unsigned int value)
+{
+ /* 0 is an unreasonable value for any timer. Stick with the default */
+ if (value == 0) {
+ ast_log(LOG_WARNING, "0 is an invalid value for ccnr_available_timer. Retaining value as %u\n", config->ccnr_available_timer);
+ return;
+ }
+ config->ccnr_available_timer = value;
+}
+
+unsigned int ast_get_cc_recall_timer(struct ast_cc_config_params *config)
+{
+ return config->cc_recall_timer;
+}
+
+void ast_set_cc_recall_timer(struct ast_cc_config_params *config, unsigned int value)
+{
+ /* 0 is an unreasonable value for any timer. Stick with the default */
+ if (value == 0) {
+ ast_log(LOG_WARNING, "0 is an invalid value for ccnr_available_timer. Retaining value as %u\n", config->cc_recall_timer);
+ return;
+ }
+ config->cc_recall_timer = value;
+}
+
+unsigned int ast_get_ccbs_available_timer(struct ast_cc_config_params *config)
+{
+ return config->ccbs_available_timer;
+}
+
+void ast_set_ccbs_available_timer(struct ast_cc_config_params *config, unsigned int value)
+{
+ /* 0 is an unreasonable value for any timer. Stick with the default */
+ if (value == 0) {
+ ast_log(LOG_WARNING, "0 is an invalid value for ccbs_available_timer. Retaining value as %u\n", config->ccbs_available_timer);
+ return;
+ }
+ config->ccbs_available_timer = value;
+}
+
+const char *ast_get_cc_agent_dialstring(struct ast_cc_config_params *config)
+{
+ return config->cc_agent_dialstring;
+}
+
+void ast_set_cc_agent_dialstring(struct ast_cc_config_params *config, const char *const value)
+{
+ if (ast_strlen_zero(value)) {
+ config->cc_agent_dialstring[0] = '\0';
+ } else {
+ ast_copy_string(config->cc_agent_dialstring, value, sizeof(config->cc_agent_dialstring));
+ }
+}
+
+unsigned int ast_get_cc_max_agents(struct ast_cc_config_params *config)
+{
+ return config->cc_max_agents;
+}
+
+void ast_set_cc_max_agents(struct ast_cc_config_params *config, unsigned int value)
+{
+ config->cc_max_agents = value;
+}
+
+unsigned int ast_get_cc_max_monitors(struct ast_cc_config_params *config)
+{
+ return config->cc_max_monitors;
+}
+
+void ast_set_cc_max_monitors(struct ast_cc_config_params *config, unsigned int value)
+{
+ config->cc_max_monitors = value;
+}
+
+const char *ast_get_cc_callback_macro(struct ast_cc_config_params *config)
+{
+ return config->cc_callback_macro;
+}
+
+void ast_set_cc_callback_macro(struct ast_cc_config_params *config, const char * const value)
+{
+ if (ast_strlen_zero(value)) {
+ config->cc_callback_macro[0] = '\0';
+ } else {
+ ast_copy_string(config->cc_callback_macro, value, sizeof(config->cc_callback_macro));
+ }
+}
+
+struct cc_monitor_backend {
+ AST_LIST_ENTRY(cc_monitor_backend) next;
+ const struct ast_cc_monitor_callbacks *callbacks;
+};
+
+AST_RWLIST_HEAD_STATIC(cc_monitor_backends, cc_monitor_backend);
+
+int ast_cc_monitor_register(const struct ast_cc_monitor_callbacks *callbacks)
+{
+ struct cc_monitor_backend *backend = ast_calloc(1, sizeof(*backend));
+
+ if (!backend) {
+ return -1;
+ }
+
+ backend->callbacks = callbacks;
+
+ AST_RWLIST_WRLOCK(&cc_monitor_backends);
+ AST_RWLIST_INSERT_TAIL(&cc_monitor_backends, backend, next);
+ AST_RWLIST_UNLOCK(&cc_monitor_backends);
+ return 0;
+}
+
+static const struct ast_cc_monitor_callbacks *find_monitor_callbacks(const char * const type)
+{
+ struct cc_monitor_backend *backend;
+ const struct ast_cc_monitor_callbacks *callbacks = NULL;
+
+ AST_RWLIST_RDLOCK(&cc_monitor_backends);
+ AST_RWLIST_TRAVERSE(&cc_monitor_backends, backend, next) {
+ if (!strcmp(backend->callbacks->type, type)) {
+ ast_log_dynamic_level(cc_logger_level, "Returning monitor backend %s\n", backend->callbacks->type);
+ callbacks = backend->callbacks;
+ break;
+ }
+ }
+ AST_RWLIST_UNLOCK(&cc_monitor_backends);
+ return callbacks;
+}
+
+void ast_cc_monitor_unregister(const struct ast_cc_monitor_callbacks *callbacks)
+{
+ struct cc_monitor_backend *backend;
+ AST_RWLIST_WRLOCK(&cc_monitor_backends);
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&cc_monitor_backends, backend, next) {
+ if (backend->callbacks == callbacks) {
+ AST_RWLIST_REMOVE_CURRENT(next);
+ ast_free(backend);
+ break;
+ }
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END;
+ AST_RWLIST_UNLOCK(&cc_monitor_backends);
+}
+
+struct cc_agent_backend {
+ AST_LIST_ENTRY(cc_agent_backend) next;
+ const struct ast_cc_agent_callbacks *callbacks;
+};
+
+AST_RWLIST_HEAD_STATIC(cc_agent_backends, cc_agent_backend);
+
+int ast_cc_agent_register(const struct ast_cc_agent_callbacks *callbacks)
+{
+ struct cc_agent_backend *backend = ast_calloc(1, sizeof(*backend));
+
+ if (!backend) {
+ return -1;
+ }
+
+ backend->callbacks = callbacks;
+ AST_RWLIST_WRLOCK(&cc_agent_backends);
+ AST_RWLIST_INSERT_TAIL(&cc_agent_backends, backend, next);
+ AST_RWLIST_UNLOCK(&cc_agent_backends);
+ return 0;
+}
+
+void ast_cc_agent_unregister(const struct ast_cc_agent_callbacks *callbacks)
+{
+ struct cc_agent_backend *backend;
+ AST_RWLIST_WRLOCK(&cc_agent_backends);
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&cc_agent_backends, backend, next) {
+ if (backend->callbacks == callbacks) {
+ AST_RWLIST_REMOVE_CURRENT(next);
+ ast_free(backend);
+ break;
+ }
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END;
+ AST_RWLIST_UNLOCK(&cc_agent_backends);
+}
+
+static const struct ast_cc_agent_callbacks *find_agent_callbacks(struct ast_channel *chan)
+{
+ struct cc_agent_backend *backend;
+ const struct ast_cc_agent_callbacks *callbacks = NULL;
+ struct ast_cc_config_params *cc_params;
+ char type[32];
+
+ cc_params = ast_channel_get_cc_config_params(chan);
+ if (!cc_params) {
+ return NULL;
+ }
+ switch (ast_get_cc_agent_policy(cc_params)) {
+ case AST_CC_AGENT_GENERIC:
+ ast_copy_string(type, "generic", sizeof(type));
+ break;
+ case AST_CC_AGENT_NATIVE:
+ ast_channel_get_cc_agent_type(chan, type, sizeof(type));
+ break;
+ default:
+ ast_log_dynamic_level(cc_logger_level, "Not returning agent callbacks since this channel is configured not to have a CC agent\n");
+ return NULL;
+ }
+
+ AST_RWLIST_RDLOCK(&cc_agent_backends);
+ AST_RWLIST_TRAVERSE(&cc_agent_backends, backend, next) {
+ if (!strcmp(backend->callbacks->type, type)) {
+ ast_log_dynamic_level(cc_logger_level, "Returning agent backend %s\n", backend->callbacks->type);
+ callbacks = backend->callbacks;
+ break;
+ }
+ }
+ AST_RWLIST_UNLOCK(&cc_agent_backends);
+ return callbacks;
+}
+
+static int cc_generic_monitor_request_cc(struct ast_cc_monitor *monitor, int *available_timer_id);
+static int cc_generic_monitor_suspend(struct ast_cc_monitor *monitor);
+static int cc_generic_monitor_status_response(struct ast_cc_monitor *monitor, enum ast_device_state devstate);
+static int cc_generic_monitor_unsuspend(struct ast_cc_monitor *monitor);
+static int cc_generic_monitor_cancel_available_timer(struct ast_cc_monitor *monitor, int *sched_id);
+static void cc_generic_monitor_destructor(void *private_data);
+
+static struct ast_cc_monitor_callbacks generic_monitor_cbs = {
+ .type = "generic",
+ .request_cc = cc_generic_monitor_request_cc,
+ .suspend = cc_generic_monitor_suspend,
+ .status_response = cc_generic_monitor_status_response,
+ .unsuspend = cc_generic_monitor_unsuspend,
+ .cancel_available_timer = cc_generic_monitor_cancel_available_timer,
+ .destructor = cc_generic_monitor_destructor,
+};
+
+struct ao2_container *generic_monitors;
+
+struct generic_monitor_instance {
+ int core_id;
+ int is_suspended;
+ int monitoring;
+ AST_LIST_ENTRY(generic_monitor_instance) next;
+};
+
+struct generic_monitor_instance_list {
+ const char *device_name;
+ enum ast_device_state current_state;
+ struct ast_event_sub *sub;
+ AST_LIST_HEAD_NOLOCK(, generic_monitor_instance) list;
+};
+
+/*!
+ * \brief private data for generic device monitor
+ */
+struct generic_monitor_pvt {
+ /*!
+ * We need the device name during destruction so we
+ * can find the appropriate item to destroy.
+ */
+ const char *device_name;
+ /*!
+ * We need the core ID for similar reasons. Once we
+ * find the appropriate item in our ao2_container, we
+ * need to remove the appropriate cc_monitor from the
+ * list of monitors.
+ */
+ int core_id;
+};
+
+static int generic_monitor_hash_fn(const void *obj, const int flags)
+{
+ const struct generic_monitor_instance_list *generic_list = obj;
+ return ast_str_hash(generic_list->device_name);
+}
+
+static int generic_monitor_cmp_fn(void *obj, void *arg, int flags)
+{
+ const struct generic_monitor_instance_list *generic_list1 = obj;
+ const struct generic_monitor_instance_list *generic_list2 = arg;
+
+ return !strcmp(generic_list1->device_name, generic_list2->device_name) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static struct generic_monitor_instance_list *find_generic_monitor_instance_list(const char * const device_name)
+{
+ struct generic_monitor_instance_list finder = {.device_name = device_name};
+
+ return ao2_t_find(generic_monitors, &finder, OBJ_POINTER, "Finding generic monitor instance list");
+}
+
+static void generic_monitor_instance_list_destructor(void *obj)
+{
+ struct generic_monitor_instance_list *generic_list = obj;
+ struct generic_monitor_instance *generic_instance;
+
+ generic_list->sub = ast_event_unsubscribe(generic_list->sub);
+ while ((generic_instance = AST_LIST_REMOVE_HEAD(&generic_list->list, next))) {
+ ast_free(generic_instance);
+ }
+ ast_free((char *)generic_list->device_name);
+}
+
+static void generic_monitor_devstate_cb(const struct ast_event *event, void *userdata);
+static struct generic_monitor_instance_list *create_new_generic_list(struct ast_cc_monitor *monitor)
+{
+ struct generic_monitor_instance_list *generic_list = ao2_t_alloc(sizeof(*generic_list),
+ generic_monitor_instance_list_destructor, "allocate generic monitor instance list");
+
+ if (!generic_list) {
+ return NULL;
+ }
+
+ if (!(generic_list->device_name = ast_strdup(monitor->interface->device_name))) {
+ cc_unref(generic_list, "Failed to strdup the monitor's device name");
+ return NULL;
+ }
+
+ if (!(generic_list->sub = ast_event_subscribe(AST_EVENT_DEVICE_STATE, generic_monitor_devstate_cb,
+ "Requesting CC", NULL, AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR,
+ monitor->interface->device_name, AST_EVENT_IE_END))) {
+ cc_unref(generic_list, "Failed to subscribe to device state");
+ return NULL;
+ }
+ generic_list->current_state = ast_device_state(monitor->interface->device_name);
+ ao2_t_link(generic_monitors, generic_list, "linking new generic monitor instance list");
+ return generic_list;
+}
+
+struct generic_tp_cb_data {
+ const char *device_name;
+ enum ast_device_state new_state;
+};
+
+static int generic_monitor_devstate_tp_cb(void *data)
+{
+ struct generic_tp_cb_data *gtcd = data;
+ enum ast_device_state new_state = gtcd->new_state;
+ enum ast_device_state previous_state = gtcd->new_state;
+ const char *monitor_name = gtcd->device_name;
+ struct generic_monitor_instance_list *generic_list;
+ struct generic_monitor_instance *generic_instance;
+
+ if (!(generic_list = find_generic_monitor_instance_list(monitor_name))) {
+ /* The most likely cause for this is that we destroyed the monitor in the
+ * time between subscribing to its device state and the time this executes.
+ * Not really a big deal.
+ */
+ ast_free((char *) gtcd->device_name);
+ ast_free(gtcd);
+ return 0;
+ }
+
+ if (generic_list->current_state == new_state) {
+ /* The device state hasn't actually changed, so we don't really care */
+ cc_unref(generic_list, "Kill reference of generic list in devstate taskprocessor callback");
+ ast_free((char *) gtcd->device_name);
+ ast_free(gtcd);
+ return 0;
+ }
+
+ previous_state = generic_list->current_state;
+ generic_list->current_state = new_state;
+
+ if ((new_state == AST_DEVICE_NOT_INUSE || new_state == AST_DEVICE_UNKNOWN) &&
+ (previous_state == AST_DEVICE_INUSE || previous_state == AST_DEVICE_UNAVAILABLE ||
+ previous_state == AST_DEVICE_BUSY)) {
+ AST_LIST_TRAVERSE(&generic_list->list, generic_instance, next) {
+ if (!generic_instance->is_suspended && generic_instance->monitoring) {
+ generic_instance->monitoring = 0;
+ ast_cc_monitor_callee_available(generic_instance->core_id, "Generic monitored party has become available");
+ break;
+ }
+ }
+ }
+ cc_unref(generic_list, "Kill reference of generic list in devstate taskprocessor callback");
+ ast_free((char *) gtcd->device_name);
+ ast_free(gtcd);
+ return 0;
+}
+
+static void generic_monitor_devstate_cb(const struct ast_event *event, void *userdata)
+{
+ /* Wow, it's cool that we've picked up on a state change, but we really want
+ * the actual work to be done in the core's taskprocessor execution thread
+ * so that all monitor operations can be serialized. Locks?! We don't need
+ * no steenkin' locks!
+ */
+ struct generic_tp_cb_data *gtcd = ast_calloc(1, sizeof(*gtcd));
+
+ if (!gtcd) {
+ return;
+ }
+
+ if (!(gtcd->device_name = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_DEVICE)))) {
+ ast_free(gtcd);
+ return;
+ }
+ gtcd->new_state = ast_event_get_ie_uint(event, AST_EVENT_IE_STATE);
+
+ if (ast_taskprocessor_push(cc_core_taskprocessor, generic_monitor_devstate_tp_cb, gtcd)) {
+ ast_free((char *)gtcd->device_name);
+ ast_free(gtcd);
+ }
+}
+
+int ast_cc_available_timer_expire(const void *data)
+{
+ struct ast_cc_monitor *monitor = (struct ast_cc_monitor *) data;
+ int res;
+ monitor->available_timer_id = -1;
+ res = ast_cc_monitor_failed(monitor->core_id, monitor->interface->device_name, "Available timer expired for monitor");
+ cc_unref(monitor, "Unref reference from scheduler\n");
+ return res;
+}
+
+static int cc_generic_monitor_request_cc(struct ast_cc_monitor *monitor, int *available_timer_id)
+{
+ struct generic_monitor_instance_list *generic_list;
+ struct generic_monitor_instance *generic_instance;
+ struct generic_monitor_pvt *gen_mon_pvt;
+ enum ast_cc_service_type service = monitor->service_offered;
+ int when;
+
+ /* First things first. Native channel drivers will have their private data allocated
+ * at the time that they tell the core that they can offer CC. Generic is quite a bit
+ * different, and we wait until this point to allocate our private data.
+ */
+ if (!(gen_mon_pvt = ast_calloc(1, sizeof(*gen_mon_pvt)))) {
+ return -1;
+ }
+
+ if (!(gen_mon_pvt->device_name = ast_strdup(monitor->interface->device_name))) {
+ ast_free(gen_mon_pvt);
+ return -1;
+ }
+
+ gen_mon_pvt->core_id = monitor->core_id;
+
+ monitor->private_data = gen_mon_pvt;
+
+ if (!(generic_list = find_generic_monitor_instance_list(monitor->interface->device_name))) {
+ if (!(generic_list = create_new_generic_list(monitor))) {
+ return -1;
+ }
+ }
+
+ if (!(generic_instance = ast_calloc(1, sizeof(*generic_instance)))) {
+ /* The generic monitor destructor will take care of the appropriate
+ * deallocations
+ */
+ cc_unref(generic_list, "Generic monitor instance failed to allocate");
+ return -1;
+ }
+ generic_instance->core_id = monitor->core_id;
+ generic_instance->monitoring = 1;
+ AST_LIST_INSERT_TAIL(&generic_list->list, generic_instance, next);
+ when = service == AST_CC_CCBS ? ast_get_ccbs_available_timer(monitor->interface->config_params) :
+ ast_get_ccnr_available_timer(monitor->interface->config_params);
+
+ *available_timer_id = ast_sched_thread_add(cc_sched_thread, when * 1000,
+ ast_cc_available_timer_expire, cc_ref(monitor, "Give the scheduler a monitor reference"));
+ if (*available_timer_id == -1) {
+ cc_unref(monitor, "Failed to schedule available timer. (monitor)");
+ cc_unref(generic_list, "Failed to schedule available timer. (generic_list)");
+ return -1;
+ }
+ ast_cc_monitor_request_acked(monitor->core_id, "Generic monitor for %s subscribed to device state.",
+ monitor->interface->device_name);
+ cc_unref(generic_list, "Finished with monitor instance reference in request cc callback");
+ return 0;
+}
+
+static int cc_generic_monitor_suspend(struct ast_cc_monitor *monitor)
+{
+ struct generic_monitor_instance_list *generic_list;
+ struct generic_monitor_instance *generic_instance;
+ enum ast_device_state state = ast_device_state(monitor->interface->device_name);
+
+ if (!(generic_list = find_generic_monitor_instance_list(monitor->interface->device_name))) {
+ return -1;
+ }
+
+ /* First we need to mark this particular monitor as being suspended. */
+ AST_LIST_TRAVERSE(&generic_list->list, generic_instance, next) {
+ if (generic_instance->core_id == monitor->core_id) {
+ generic_instance->is_suspended = 1;
+ break;
+ }
+ }
+
+ /* If the device being suspended is currently in use, then we don't need to
+ * take any further actions
+ */
+ if (state != AST_DEVICE_NOT_INUSE && state != AST_DEVICE_UNKNOWN) {
+ cc_unref(generic_list, "Device is in use. Nothing to do. Unref generic list.");
+ return 0;
+ }
+
+ /* If the device is not in use, though, then it may be possible to report the
+ * device's availability using a different monitor which is monitoring the
+ * same device
+ */
+
+ AST_LIST_TRAVERSE(&generic_list->list, generic_instance, next) {
+ if (!generic_instance->is_suspended) {
+ ast_cc_monitor_callee_available(generic_instance->core_id, "Generic monitored party has become available");
+ break;
+ }
+ }
+ cc_unref(generic_list, "Done with generic list in suspend callback");
+ return 0;
+}
+
+static int cc_generic_monitor_status_response(struct ast_cc_monitor *monitor, enum ast_device_state devstate)
+{
+ /* The generic monitor will never issue a status request of the other side's agent.
+ * If this somehow gets called, something really fishy is going on.
+ */
+ ast_log(LOG_WARNING, "Why has a generic monitor's status_response callback been called? CoreID is %d\n", monitor->core_id);
+ return 0;
+}
+
+static int cc_generic_monitor_unsuspend(struct ast_cc_monitor *monitor)
+{
+ struct generic_monitor_instance *generic_instance;
+ struct generic_monitor_instance_list *generic_list = find_generic_monitor_instance_list(monitor->interface->device_name);
+ enum ast_device_state state = ast_device_state(monitor->interface->device_name);
+
+ if (!generic_list) {
+ return -1;
+ }
+ /* If the device is currently available, we can immediately announce
+ * its availability
+ */
+ if (state == AST_DEVICE_NOT_INUSE || state == AST_DEVICE_UNKNOWN) {
+ ast_cc_monitor_callee_available(monitor->core_id, "Generic monitored party has become available");
+ }
+
+ /* In addition, we need to mark this generic_monitor_instance as not being suspended anymore */
+ AST_LIST_TRAVERSE(&generic_list->list, generic_instance, next) {
+ if (generic_instance->core_id == monitor->core_id) {
+ generic_instance->is_suspended = 0;
+ generic_instance->monitoring = 1;
+ break;
+ }
+ }
+ cc_unref(generic_list, "Done with generic list in cc_generic_monitor_unsuspend");
+ return 0;
+}
+
+static int cc_generic_monitor_cancel_available_timer(struct ast_cc_monitor *monitor, int *sched_id)
+{
+ ast_assert(sched_id != NULL);
+
+ if (*sched_id == -1) {
+ return 0;
+ }
+
+ ast_log_dynamic_level(cc_logger_level, "Core %d: Canceling generic monitor available timer for monitor %s\n",
+ monitor->core_id, monitor->interface->device_name);
+ if (!ast_sched_thread_del(cc_sched_thread, *sched_id)) {
+ cc_unref(monitor, "Remove scheduler's reference to the monitor");
+ }
+ *sched_id = -1;
+ return 0;
+}
+
+static void cc_generic_monitor_destructor(void *private_data)
+{
+ struct generic_monitor_pvt *gen_mon_pvt = private_data;
+ struct generic_monitor_instance_list *generic_list;
+ struct generic_monitor_instance *generic_instance;
+
+ if (!private_data) {
+ /* If the private data is NULL, that means that the monitor hasn't even
+ * been created yet, but that the destructor was called. While this sort
+ * of behavior is useful for native monitors, with a generic one, there is
+ * nothing in particular to do.
+ */
+ return;
+ }
+
+ ast_log_dynamic_level(cc_logger_level, "Core %d: Destroying generic monitor %s\n",
+ gen_mon_pvt->core_id, gen_mon_pvt->device_name);
+
+ if (!(generic_list = find_generic_monitor_instance_list(gen_mon_pvt->device_name))) {
+ /* If there's no generic list, that means that the monitor is being destroyed
+ * before we actually got to request CC. Not a biggie. Same in the situation
+ * below if the list traversal should complete without finding an entry.
+ */
+ ast_free((char *)gen_mon_pvt->device_name);
+ ast_free(gen_mon_pvt);
+ return;
+ }
+
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&generic_list->list, generic_instance, next) {
+ if (generic_instance->core_id == gen_mon_pvt->core_id) {
+ AST_LIST_REMOVE_CURRENT(next);
+ ast_free(generic_instance);
+ break;
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+
+ if (AST_LIST_EMPTY(&generic_list->list)) {
+ /* No more monitors with this device name exist. Time to unlink this
+ * list from the container
+ */
+ ao2_t_unlink(generic_monitors, generic_list, "Generic list is empty. Unlink it from the container");
+ }
+ cc_unref(generic_list, "Done with generic list in generic monitor destructor");
+ ast_free((char *)gen_mon_pvt->device_name);
+ ast_free(gen_mon_pvt);
+}
+
+static void cc_interface_destroy(void *data)
+{
+ struct ast_cc_interface *interface = data;
+ ast_log_dynamic_level(cc_logger_level, "Destroying cc interface %s\n", interface->device_name);
+ ast_cc_config_params_destroy(interface->config_params);
+}
+
+/*!
+ * \brief Data regarding an extension monitor's child's dialstrings
+ *
+ * \details
+ * In developing CCSS, we had most aspects of its operation finished,
+ * but there was one looming problem that we had failed to get right.
+ * In our design document, we stated that when a CC recall occurs, all
+ * endpoints that had been dialed originally would be called back.
+ * Unfortunately, our implementation only allowed for devices which had
+ * active monitors to inhabit the CC_INTERFACES channel variable, thus
+ * making the automated recall only call monitored devices.
+ *
+ * Devices that were not CC-capable, or devices which failed CC at some
+ * point during the process would not make it into the CC_INTERFACES
+ * channel variable. This struct is meant as a remedy for the problem.
+ */
+struct extension_child_dialstring {
+ /*!
+ * \brief the original dialstring used to call a particular device
+ *
+ * \details
+ * When someone dials a particular endpoint, the dialstring used in
+ * the dialplan is copied into this buffer. What's important here is
+ * that this is the ORIGINAL dialstring, not the dialstring saved on
+ * a device monitor. The dialstring on a device monitor is what should
+ * be used when recalling that device. The two dialstrings may not be
+ * the same.
+ *
+ * By keeping a copy of the original dialstring used, we can fall back
+ * to using it if the device either does not ever offer CC or if the
+ * device at some point fails for some reason, such as a timer expiration.
+ */
+ char original_dialstring[AST_CHANNEL_NAME];
+ /*!
+ * \brief The name of the device being dialed
+ *
+ * \details
+ * This serves mainly as a key when searching for a particular dialstring.
+ * For instance, let's say that we have called device SIP/400@somepeer. This
+ * device offers call completion, but then due to some unforeseen circumstance,
+ * this device backs out and makes CC unavailable. When that happens, we need
+ * to find the dialstring that corresponds to that device, and we use the
+ * stored device name as a way to find it.
+ *
+ * Note that there is one particular case where the device name stored here
+ * will be empty. This is the case where we fail to request a channel, but we
+ * still can make use of generic call completion. In such a case, since we never
+ * were able to request the channel, we can't find what its device name is. In
+ * this case, however, it is not important because the dialstring is guaranteed
+ * to be the same both here and in the device monitor.
+ */
+ char device_name[AST_CHANNEL_NAME];
+ /*!
+ * \brief Is this structure valid for use in CC_INTERFACES?
+ *
+ * \details
+ * When this structure is first created, all information stored here is planned
+ * to be used, so we set the is_valid flag. However, if a device offers call
+ * completion, it will potentially have its own dialstring to use for the recall,
+ * so we find this structure and clear the is_valid flag. By clearing the is_valid
+ * flag, we won't try to populate the CC_INTERFACES variable with the dialstring
+ * stored in this struct. Now, if later, the device which had offered CC should fail,
+ * perhaps due to a timer expiration, then we need to re-set the is_valid flag. This
+ * way, we still will end up placing a call to the device again, and the dialstring
+ * used will be the same as was originally used.
+ */
+ int is_valid;
+ AST_LIST_ENTRY(extension_child_dialstring) next;
+};
+
+/*!
+ * \brief Private data for an extension monitor
+ */
+struct extension_monitor_pvt {
+ AST_LIST_HEAD_NOLOCK(, extension_child_dialstring) child_dialstrings;
+};
+
+static void cc_extension_monitor_destructor(void *private_data)
+{
+ struct extension_monitor_pvt *extension_pvt = private_data;
+ struct extension_child_dialstring *child_dialstring;
+
+ /* This shouldn't be possible, but I'm paranoid */
+ if (!extension_pvt) {
+ return;
+ }
+
+ while ((child_dialstring = AST_LIST_REMOVE_HEAD(&extension_pvt->child_dialstrings, next))) {
+ ast_free(child_dialstring);
+ }
+ ast_free(extension_pvt);
+}
+
+static void cc_monitor_destroy(void *data)
+{
+ struct ast_cc_monitor *monitor = data;
+ /* During the monitor creation process, it is possible for this
+ * function to be called prior to when callbacks are assigned
+ * to the monitor. Also, extension monitors do not have callbacks
+ * assigned to them, so we wouldn't want to segfault when we try
+ * to destroy one of them.
+ */
+ ast_log_dynamic_level(cc_logger_level, "Core %d: Calling destructor for monitor %s\n",
+ monitor->core_id, monitor->interface->device_name);
+ if (monitor->interface->monitor_class == AST_CC_EXTENSION_MONITOR) {
+ cc_extension_monitor_destructor(monitor->private_data);
+ }
+ if (monitor->callbacks) {
+ monitor->callbacks->destructor(monitor->private_data);
+ }
+ cc_unref(monitor->interface, "Unreffing tree's reference to interface");
+ ast_free(monitor->dialstring);
+}
+
+static void cc_interface_tree_destroy(void *data)
+{
+ struct cc_monitor_tree *cc_interface_tree = data;
+ struct ast_cc_monitor *monitor;
+ while ((monitor = AST_LIST_REMOVE_HEAD(cc_interface_tree, next))) {
+ if (monitor->callbacks) {
+ monitor->callbacks->cancel_available_timer(monitor, &monitor->available_timer_id);
+ }
+ cc_unref(monitor, "Destroying all monitors");
+ }
+ AST_LIST_HEAD_DESTROY(cc_interface_tree);
+}
+
+/*!
+ * This counter is used for assigning unique ids
+ * to CC-enabled dialed interfaces.
+ */
+static int dialed_cc_interface_counter;
+
+/*!
+ * \internal
+ * \brief data stored in CC datastore
+ *
+ * The datastore creates a list of interfaces that were
+ * dialed, including both extensions and devices. In addition
+ * to the intrinsic data of the tree, some extra information
+ * is needed for use by app_dial.
+ */
+struct dialed_cc_interfaces {
+ /*!
+ * This value serves a dual-purpose. When dial starts, if the
+ * dialed_cc_interfaces datastore currently exists on the calling
+ * channel, then the dial_parent_id will serve as a means of
+ * letting the new extension cc_monitor we create know
+ * who his parent is. This value will be the extension
+ * cc_monitor that dialed the local channel that resulted
+ * in the new Dial app being called.
+ *
+ * In addition, once an extension cc_monitor is created,
+ * the dial_parent_id will be changed to the id of that newly
+ * created interface. This way, device interfaces created from
+ * receiving AST_CONTROL_CC frames can use this field to determine
+ * who their parent extension interface should be.
+ */
+ unsigned int dial_parent_id;
+ /*!
+ * Identifier for the potential CC request that may be made
+ * based on this call. Even though an instance of the core may
+ * not be made (since the caller may not request CC), we allocate
+ * a new core_id at the beginning of the call so that recipient
+ * channel drivers can have the information handy just in case
+ * the caller does end up requesting CC.
+ */
+ int core_id;
+ /*!
+ * When a new Dial application is started, and the datastore
+ * already exists on the channel, we can determine if we
+ * should be adding any new interface information to tree.
+ */
+ char ignore;
+ /*!
+ * When it comes time to offer CC to the caller, we only want to offer
+ * it to the original incoming channel. For nested Dials and outbound
+ * channels, it is incorrect to attempt such a thing. This flag indicates
+ * if the channel to which this datastore is attached may be legally
+ * offered CC when the call is finished.
+ */
+ char is_original_caller;
+ /*!
+ * Reference-counted "tree" of interfaces.
+ */
+ struct cc_monitor_tree *interface_tree;
+};
+
+/*!
+ * \internal
+ * \brief Destructor function for cc_interfaces datastore
+ *
+ * This function will free the actual datastore and drop
+ * the refcount for the monitor tree by one. In cases
+ * where CC can actually be used, this unref will not
+ * result in the destruction of the monitor tree, because
+ * the CC core will still have a reference.
+ *
+ * \param data The dialed_cc_interfaces struct to destroy
+ */
+static void dialed_cc_interfaces_destroy(void *data)
+{
+ struct dialed_cc_interfaces *cc_interfaces = data;
+ cc_unref(cc_interfaces->interface_tree, "Unref dial's ref to monitor tree");
+ ast_free(cc_interfaces);
+}
+
+/*!
+ * \internal
+ * \brief Duplicate callback for cc_interfaces datastore
+ *
+ * Integers are copied by value, but the monitor tree
+ * is done via a shallow copy and a bump of the refcount.
+ * This way, sub-Dials will be appending interfaces onto
+ * the same list as this call to Dial.
+ *
+ * \param data The old dialed_cc_interfaces we want to copy
+ * \retval NULL Could not allocate memory for new dialed_cc_interfaces
+ * \retval non-NULL The new copy of the dialed_cc_interfaces
+ */
+static void *dialed_cc_interfaces_duplicate(void *data)
+{
+ struct dialed_cc_interfaces *old_cc_interfaces = data;
+ struct dialed_cc_interfaces *new_cc_interfaces = ast_calloc(1, sizeof(*new_cc_interfaces));
+ if (!new_cc_interfaces) {
+ return NULL;
+ }
+ new_cc_interfaces->ignore = old_cc_interfaces->ignore;
+ new_cc_interfaces->dial_parent_id = old_cc_interfaces->dial_parent_id;
+ new_cc_interfaces->is_original_caller = 0;
+ cc_ref(old_cc_interfaces->interface_tree, "New ref due to duplication of monitor tree");
+ new_cc_interfaces->core_id = old_cc_interfaces->core_id;
+ new_cc_interfaces->interface_tree = old_cc_interfaces->interface_tree;
+ return new_cc_interfaces;
+}
+
+/*!
+ * \internal
+ * \brief information regarding the dialed_cc_interfaces datastore
+ *
+ * The dialed_cc_interfaces datastore is responsible for keeping track
+ * of what CC-enabled interfaces have been dialed by the caller. For
+ * more information regarding the actual structure of the tree, see
+ * the documentation provided in include/asterisk/ccss.h
+ */
+static const struct ast_datastore_info dialed_cc_interfaces_info = {
+ .type = "Dial CC Interfaces",
+ .duplicate = dialed_cc_interfaces_duplicate,
+ .destroy = dialed_cc_interfaces_destroy,
+};
+
+static struct extension_monitor_pvt *extension_monitor_pvt_init(void)
+{
+ struct extension_monitor_pvt *ext_pvt = ast_calloc(1, sizeof(*ext_pvt));
+ if (!ext_pvt) {
+ return NULL;
+ }
+ AST_LIST_HEAD_INIT_NOLOCK(&ext_pvt->child_dialstrings);
+ return ext_pvt;
+}
+
+void ast_cc_extension_monitor_add_dialstring(struct ast_channel *incoming, const char * const dialstring, const char * const device_name)
+{
+ struct ast_datastore *cc_datastore;
+ struct dialed_cc_interfaces *cc_interfaces;
+ struct ast_cc_monitor *monitor;
+ struct extension_monitor_pvt *extension_pvt;
+ struct extension_child_dialstring *child_dialstring;
+ struct cc_monitor_tree *interface_tree;
+ int id;
+
+ ast_channel_lock(incoming);
+ if (!(cc_datastore = ast_channel_datastore_find(incoming, &dialed_cc_interfaces_info, NULL))) {
+ ast_channel_unlock(incoming);
+ return;
+ }
+
+ cc_interfaces = cc_datastore->data;
+ interface_tree = cc_interfaces->interface_tree;
+ id = cc_interfaces->dial_parent_id;
+ ast_channel_unlock(incoming);
+
+ AST_LIST_LOCK(interface_tree);
+ AST_LIST_TRAVERSE(interface_tree, monitor, next) {
+ if (monitor->id == id) {
+ break;
+ }
+ }
+
+ if (!monitor) {
+ AST_LIST_UNLOCK(interface_tree);
+ return;
+ }
+
+ extension_pvt = monitor->private_data;
+ if (!(child_dialstring = ast_calloc(1, sizeof(*child_dialstring)))) {
+ AST_LIST_UNLOCK(interface_tree);
+ return;
+ }
+ ast_copy_string(child_dialstring->original_dialstring, dialstring, sizeof(child_dialstring->original_dialstring));
+ ast_copy_string(child_dialstring->device_name, device_name, sizeof(child_dialstring->device_name));
+ child_dialstring->is_valid = 1;
+ AST_LIST_INSERT_TAIL(&extension_pvt->child_dialstrings, child_dialstring, next);
+ AST_LIST_UNLOCK(interface_tree);
+}
+
+static void cc_extension_monitor_change_is_valid(struct cc_core_instance *core_instance, unsigned int parent_id, const char * const device_name, int is_valid)
+{
+ struct ast_cc_monitor *monitor_iter;
+ struct extension_monitor_pvt *extension_pvt;
+ struct extension_child_dialstring *child_dialstring;
+
+ AST_LIST_TRAVERSE(core_instance->monitors, monitor_iter, next) {
+ if (monitor_iter->id == parent_id) {
+ break;
+ }
+ }
+
+ if (!monitor_iter) {
+ return;
+ }
+ extension_pvt = monitor_iter->private_data;
+
+ AST_LIST_TRAVERSE(&extension_pvt->child_dialstrings, child_dialstring, next) {
+ if (!strcmp(child_dialstring->device_name, device_name)) {
+ child_dialstring->is_valid = is_valid;
+ break;
+ }
+ }
+}
+
+/*!
+ * \internal
+ * \brief Allocate and initialize an "extension" interface for CC purposes
+ *
+ * When app_dial starts, this function is called in order to set up the
+ * information about the extension in which this Dial is occurring. Any
+ * devices dialed will have this particular cc_monitor as a parent.
+ *
+ * \param exten Extension from which Dial is occurring
+ * \param context Context to which exten belongs
+ * \param parent_id What should we set the parent_id of this interface to?
+ * \retval NULL Memory allocation failure
+ * \retval non-NULL The newly-created cc_monitor for the extension
+ */
+static struct ast_cc_monitor *cc_extension_monitor_init(const char * const exten, const char * const context, const unsigned int parent_id)
+{
+ struct ast_str *str = ast_str_alloca(2 * AST_MAX_EXTENSION);
+ struct ast_cc_interface *cc_interface;
+ struct ast_cc_monitor *monitor;
+
+ ast_str_set(&str, 0, "%s@%s", exten, context);
+
+ if (!(cc_interface = ao2_t_alloc(sizeof(*cc_interface) + ast_str_strlen(str), cc_interface_destroy,
+ "Allocating new ast_cc_interface"))) {
+ return NULL;
+ }
+
+ if (!(monitor = ao2_t_alloc(sizeof(*monitor), cc_monitor_destroy, "Allocating new ast_cc_monitor"))) {
+ cc_unref(cc_interface, "failed to allocate the monitor, so unref the interface");
+ return NULL;
+ }
+
+ if (!(monitor->private_data = extension_monitor_pvt_init())) {
+ cc_unref(monitor, "Failed to initialize extension monitor private data. uref monitor");
+ cc_unref(cc_interface, "Failed to initialize extension monitor private data. unref cc_interface");
+ }
+
+ monitor->id = ast_atomic_fetchadd_int(&dialed_cc_interface_counter, +1);
+ monitor->parent_id = parent_id;
+ cc_interface->monitor_type = "extension";
+ cc_interface->monitor_class = AST_CC_EXTENSION_MONITOR;
+ strcpy(cc_interface->device_name, ast_str_buffer(str));
+ monitor->interface = cc_interface;
+ ast_log_dynamic_level(cc_logger_level, "Created an extension cc interface for '%s' with id %d and parent %d\n", cc_interface->device_name, monitor->id, monitor->parent_id);
+ return monitor;
+}
+
+/*!
+ * \internal
+ * \brief allocate dialed_cc_interfaces datastore and initialize fields
+ *
+ * This function is called when Situation 1 occurs in ast_cc_call_init.
+ * See that function for more information on what Situation 1 is.
+ *
+ * In this particular case, we have to do a lot of memory allocation in order
+ * to create the datastore, the data for the datastore, the tree of interfaces
+ * that we'll be adding to, and the initial extension interface for this Dial
+ * attempt.
+ *
+ * \param chan The channel onto which the datastore should be added.
+ * \retval -1 An error occurred
+ * \retval 0 Success
+ */
+static int cc_interfaces_datastore_init(struct ast_channel *chan) {
+ struct dialed_cc_interfaces *interfaces;
+ struct ast_cc_monitor *monitor;
+ struct ast_datastore *dial_cc_datastore;
+
+ /*XXX This may be a bit controversial. In an attempt to not allocate
+ * extra resources, I make sure that a future request will be within
+ * limits. The problem here is that it is reasonable to think that
+ * even if we're not within the limits at this point, we may be by
+ * the time the requestor will have made his request. This may be
+ * deleted at some point.
+ */
+ if (!ast_cc_request_is_within_limits()) {
+ return 0;
+ }
+
+ if (!(interfaces = ast_calloc(1, sizeof(*interfaces)))) {
+ return -1;
+ }
+
+ if (!(monitor = cc_extension_monitor_init(S_OR(chan->macroexten, chan->exten), S_OR(chan->macrocontext, chan->context), 0))) {
+ ast_free(interfaces);
+ return -1;
+ }
+
+ if (!(dial_cc_datastore = ast_datastore_alloc(&dialed_cc_interfaces_info, NULL))) {
+ cc_unref(monitor, "Could not allocate the dialed interfaces datastore. Unreffing monitor");
+ ast_free(interfaces);
+ return -1;
+ }
+
+ if (!(interfaces->interface_tree = ao2_t_alloc(sizeof(*interfaces->interface_tree), cc_interface_tree_destroy,
+ "Allocate monitor tree"))) {
+ ast_datastore_free(dial_cc_datastore);
+ cc_unref(monitor, "Could not allocate monitor tree on dialed interfaces datastore. Unreffing monitor");
+ ast_free(interfaces);
+ return -1;
+ }
+
+ /* Finally, all that allocation is done... */
+ AST_LIST_HEAD_INIT(interfaces->interface_tree);
+ AST_LIST_INSERT_TAIL(interfaces->interface_tree, monitor, next);
+ cc_ref(monitor, "List's reference to extension monitor");
+ dial_cc_datastore->data = interfaces;
+ dial_cc_datastore->inheritance = DATASTORE_INHERIT_FOREVER;
+ interfaces->dial_parent_id = monitor->id;
+ interfaces->core_id = monitor->core_id = ast_atomic_fetchadd_int(&core_id_counter, +1);
+ interfaces->is_original_caller = 1;
+ ast_channel_lock(chan);
+ ast_channel_datastore_add(chan, dial_cc_datastore);
+ ast_channel_unlock(chan);
+ cc_unref(monitor, "Unreffing allocation's reference");
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Call a monitor's destructor before the monitor has been allocated
+ * \since 1.8
+ *
+ * \param monitor_type The type of monitor callbacks to use when calling the destructor
+ * \param private_data Data allocated by a channel driver that must be freed
+ *
+ * \details
+ * I'll admit, this is a bit evil.
+ *
+ * When a channel driver determines that it can offer a call completion service to
+ * a caller, it is very likely that the channel driver will need to allocate some
+ * data so that when the time comes to request CC, the channel driver will have the
+ * necessary data at hand.
+ *
+ * The problem is that there are many places where failures may occur before the monitor
+ * has been properly allocated and had its callbacks assigned to it. If one of these
+ * failures should occur, then we still need to let the channel driver know that it
+ * must destroy the data that it allocated.
+ *
+ * \return Nothing
+ */
+static void call_destructor_with_no_monitor(const char * const monitor_type, void *private_data)
+{
+ const struct ast_cc_monitor_callbacks *monitor_callbacks = find_monitor_callbacks(monitor_type);
+
+ if (!monitor_callbacks) {
+ return;
+ }
+
+ monitor_callbacks->destructor(private_data);
+}
+
+/*!
+ * \internal
+ * \brief Allocate and intitialize a device cc_monitor
+ *
+ * For all intents and purposes, this is the same as
+ * cc_extension_monitor_init, except that there is only
+ * a single parameter used for naming the interface.
+ *
+ * This function is called when handling AST_CONTROL_CC frames.
+ * The device has reported that CC is possible, so we add it
+ * to the interface_tree.
+ *
+ * Note that it is not necessarily erroneous to add the same
+ * device to the tree twice. If the same device is called by
+ * two different extension during the same call, then
+ * that is a legitimate situation. Of course, I'm pretty sure
+ * the dialed_interfaces global datastore will not allow that
+ * to happen anyway.
+ *
+ * \param device_name The name of the device being added to the tree
+ * \param dialstring The dialstring used to dial the device being added
+ * \param parent_id The parent of this new tree node.
+ * \retval NULL Memory allocation failure
+ * \retval non-NULL The new ast_cc_interface created.
+ */
+static struct ast_cc_monitor *cc_device_monitor_init(const char * const device_name, const char * const dialstring, const struct cc_control_payload *cc_data, int core_id)
+{
+ struct ast_cc_interface *cc_interface;
+ struct ast_cc_monitor *monitor;
+ size_t device_name_len = strlen(device_name);
+ int parent_id = cc_data->parent_interface_id;
+
+ if (!(cc_interface = ao2_t_alloc(sizeof(*cc_interface) + device_name_len, cc_interface_destroy,
+ "Allocating new ast_cc_interface"))) {
+ return NULL;
+ }
+
+ if (!(cc_interface->config_params = ast_cc_config_params_init())) {
+ cc_unref(cc_interface, "Failed to allocate config params, unref interface");
+ return NULL;
+ }
+
+ if (!(monitor = ao2_t_alloc(sizeof(*monitor), cc_monitor_destroy, "Allocating new ast_cc_monitor"))) {
+ cc_unref(cc_interface, "Failed to allocate monitor, unref interface");
+ return NULL;
+ }
+
+ if (!(monitor->dialstring = ast_strdup(dialstring))) {
+ cc_unref(monitor, "Failed to copy dialable name. Unref monitor");
+ cc_unref(cc_interface, "Failed to copy dialable name");
+ return NULL;
+ }
+
+ if (!(monitor->callbacks = find_monitor_callbacks(cc_data->monitor_type))) {
+ cc_unref(monitor, "Failed to find monitor callbacks. Unref monitor");
+ cc_unref(cc_interface, "Failed to find monitor callbacks");
+ return NULL;
+ }
+
+ strcpy(cc_interface->device_name, device_name);
+ monitor->id = ast_atomic_fetchadd_int(&dialed_cc_interface_counter, +1);
+ monitor->parent_id = parent_id;
+ monitor->core_id = core_id;
+ monitor->service_offered = cc_data->service;
+ monitor->private_data = cc_data->private_data;
+ cc_interface->monitor_type = cc_data->monitor_type;
+ cc_interface->monitor_class = AST_CC_DEVICE_MONITOR;
+ monitor->interface = cc_interface;
+ monitor->available_timer_id = -1;
+ ast_cc_copy_config_params(cc_interface->config_params, &cc_data->config_params);
+ ast_log_dynamic_level(cc_logger_level, "Core %d: Created a device cc interface for '%s' with id %d and parent %d\n",
+ monitor->core_id, cc_interface->device_name, monitor->id, monitor->parent_id);
+ return monitor;
+}
+
+/*!
+ * \details
+ * Unless we are ignoring CC for some reason, we will always
+ * call this function when we read an AST_CONTROL_CC frame
+ * from an outbound channel.
+ *
+ * This function will call cc_device_monitor_init to
+ * create the new cc_monitor for the device from which
+ * we read the frame. In addition, the new device will be added
+ * to the monitor tree on the dialed_cc_interfaces datastore
+ * on the inbound channel.
+ *
+ * If this is the first AST_CONTROL_CC frame that we have handled
+ * for this call, then we will also initialize the CC core for
+ * this call.
+ */
+void ast_handle_cc_control_frame(struct ast_channel *inbound, struct ast_channel *outbound, void *frame_data)
+{
+ char *device_name;
+ char *dialstring;
+ struct ast_cc_monitor *monitor;
+ struct ast_datastore *cc_datastore;
+ struct dialed_cc_interfaces *cc_interfaces;
+ struct cc_control_payload *cc_data = frame_data;
+ struct cc_core_instance *core_instance;
+
+ device_name = cc_data->device_name;
+ dialstring = cc_data->dialstring;
+
+ ast_channel_lock(inbound);
+ if (!(cc_datastore = ast_channel_datastore_find(inbound, &dialed_cc_interfaces_info, NULL))) {
+ ast_log(LOG_WARNING, "Unable to retrieve CC datastore while processing CC frame from '%s'. CC services will be unavailable.\n", device_name);
+ ast_channel_unlock(inbound);
+ call_destructor_with_no_monitor(cc_data->monitor_type, cc_data->private_data);
+ return;
+ }
+
+ cc_interfaces = cc_datastore->data;
+
+ if (cc_interfaces->ignore) {
+ ast_channel_unlock(inbound);
+ call_destructor_with_no_monitor(cc_data->monitor_type, cc_data->private_data);
+ return;
+ }
+
+ if (!cc_interfaces->is_original_caller) {
+ /* If the is_original_caller is not set on the *inbound* channel, then
+ * it must be a local channel. As such, we do not want to create a core instance
+ * or an agent for the local channel. Instead, we want to pass this along to the
+ * other side of the local channel so that the original caller can benefit.
+ */
+ ast_channel_unlock(inbound);
+ ast_indicate_data(inbound, AST_CONTROL_CC, cc_data, sizeof(*cc_data));
+ return;
+ }
+
+ core_instance = find_cc_core_instance(cc_interfaces->core_id);
+ if (!core_instance) {
+ core_instance = cc_core_init_instance(inbound, cc_interfaces->interface_tree,
+ cc_interfaces->core_id, cc_data);
+ if (!core_instance) {
+ cc_interfaces->ignore = 1;
+ ast_channel_unlock(inbound);
+ call_destructor_with_no_monitor(cc_data->monitor_type, cc_data->private_data);
+ return;
+ }
+ }
+
+ ast_channel_unlock(inbound);
+
+ /* Yeah this kind of sucks, but luckily most people
+ * aren't dialing thousands of interfaces on every call
+ *
+ * This traversal helps us to not create duplicate monitors in
+ * case a device queues multiple CC control frames.
+ */
+ AST_LIST_LOCK(cc_interfaces->interface_tree);
+ AST_LIST_TRAVERSE(cc_interfaces->interface_tree, monitor, next) {
+ if (!strcmp(monitor->interface->device_name, device_name)) {
+ ast_log_dynamic_level(cc_logger_level, "Core %d: Device %s sent us multiple CC control frames. Ignoring those beyond the first.\n",
+ core_instance->core_id, device_name);
+ AST_LIST_UNLOCK(cc_interfaces->interface_tree);
+ cc_unref(core_instance, "Returning early from ast_handle_cc_control_frame. Unref core_instance");
+ call_destructor_with_no_monitor(cc_data->monitor_type, cc_data->private_data);
+ return;
+ }
+ }
+ AST_LIST_UNLOCK(cc_interfaces->interface_tree);
+
+ if (!(monitor = cc_device_monitor_init(device_name, dialstring, cc_data, core_instance->core_id))) {
+ ast_log(LOG_WARNING, "Unable to create CC device interface for '%s'. CC services will be unavailable on this interface.\n", device_name);
+ cc_unref(core_instance, "Returning early from ast_handle_cc_control_frame. Unref core_instance");
+ call_destructor_with_no_monitor(cc_data->monitor_type, cc_data->private_data);
+ return;
+ }
+
+ AST_LIST_LOCK(cc_interfaces->interface_tree);
+ cc_ref(monitor, "monitor tree's reference to the monitor");
+ AST_LIST_INSERT_TAIL(cc_interfaces->interface_tree, monitor, next);
+ AST_LIST_UNLOCK(cc_interfaces->interface_tree);
+
+ cc_extension_monitor_change_is_valid(core_instance, monitor->parent_id, monitor->interface->device_name, 0);
+
+ manager_event(EVENT_FLAG_CC, "CCAvailable",
+ "CoreID: %d\r\n"
+ "Callee: %s\r\n"
+ "Service: %s\r\n",
+ cc_interfaces->core_id, device_name, cc_service_to_string(cc_data->service)
+ );
+
+ cc_unref(core_instance, "Done with core_instance after handling CC control frame");
+ cc_unref(monitor, "Unref reference from allocating monitor");
+}
+
+int ast_cc_call_init(struct ast_channel *chan, int *ignore_cc)
+{
+ /* There are three situations to deal with here:
+ *
+ * 1. The channel does not have a dialed_cc_interfaces datastore on
+ * it. This means that this is the first time that Dial has
+ * been called. We need to create/initialize the datastore.
+ *
+ * 2. The channel does have a cc_interface datastore on it and
+ * the "ignore" indicator is 0. This means that a Local channel
+ * was called by a "parent" dial. We can check the datastore's
+ * parent field to see who the root of this particular dial tree
+ * is.
+ *
+ * 3. The channel does have a cc_interface datastore on it and
+ * the "ignore" indicator is 1. This means that a second Dial call
+ * is being made from an extension. In this case, we do not
+ * want to make any additions/modifications to the datastore. We
+ * will instead set a flag to indicate that CCSS is completely
+ * disabled for this Dial attempt.
+ */
+
+ struct ast_datastore *cc_interfaces_datastore;
+ struct dialed_cc_interfaces *interfaces;
+ struct ast_cc_monitor *monitor;
+ struct ast_cc_config_params *cc_params;
+
+ ast_channel_lock(chan);
+
+ cc_params = ast_channel_get_cc_config_params(chan);
+ if (!cc_params) {
+ ast_channel_unlock(chan);
+ return -1;
+ }
+ if (ast_get_cc_agent_policy(cc_params) == AST_CC_AGENT_NEVER) {
+ /* We can't offer CC to this caller anyway, so don't bother with CC on this call
+ */
+ *ignore_cc = 1;
+ ast_channel_unlock(chan);
+ ast_log_dynamic_level(cc_logger_level, "Agent policy for %s is 'never'. CC not possible\n", chan->name);
+ return 0;
+ }
+
+ if (!(cc_interfaces_datastore = ast_channel_datastore_find(chan, &dialed_cc_interfaces_info, NULL))) {
+ /* Situation 1 has occurred */
+ ast_channel_unlock(chan);
+ return cc_interfaces_datastore_init(chan);
+ }
+ interfaces = cc_interfaces_datastore->data;
+ ast_channel_unlock(chan);
+
+ if (interfaces->ignore) {
+ /* Situation 3 has occurred */
+ *ignore_cc = 1;
+ ast_log_dynamic_level(cc_logger_level, "Datastore is present with ignore flag set. Ignoring CC offers on this call\n");
+ return 0;
+ }
+
+ /* Situation 2 has occurred */
+ if (!(monitor = cc_extension_monitor_init(S_OR(chan->macroexten, chan->exten),
+ S_OR(chan->macrocontext, chan->context), interfaces->dial_parent_id))) {
+ return -1;
+ }
+ monitor->core_id = interfaces->core_id;
+ AST_LIST_LOCK(interfaces->interface_tree);
+ cc_ref(monitor, "monitor tree's reference to the monitor");
+ AST_LIST_INSERT_TAIL(interfaces->interface_tree, monitor, next);
+ AST_LIST_UNLOCK(interfaces->interface_tree);
+ interfaces->dial_parent_id = monitor->id;
+ cc_unref(monitor, "Unref monitor's allocation reference");
+ return 0;
+}
+
+int ast_cc_request_is_within_limits(void)
+{
+ return cc_request_count < global_cc_max_requests;
+}
+
+int ast_cc_get_current_core_id(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore;
+ struct dialed_cc_interfaces *cc_interfaces;
+ int core_id_return;
+
+ ast_channel_lock(chan);
+ if (!(datastore = ast_channel_datastore_find(chan, &dialed_cc_interfaces_info, NULL))) {
+ ast_channel_unlock(chan);
+ return -1;
+ }
+
+ cc_interfaces = datastore->data;
+ core_id_return = cc_interfaces->ignore ? -1 : cc_interfaces->core_id;
+ ast_channel_unlock(chan);
+ return core_id_return;
+
+}
+
+static long count_agents(const char * const caller, const int core_id_exception)
+{
+ struct count_agents_cb_data data = {.core_id_exception = core_id_exception,};
+
+ ao2_t_callback_data(cc_core_instances, OBJ_NODATA, count_agents_cb, (char *)caller, &data, "Counting agents");
+ ast_log_dynamic_level(cc_logger_level, "Counted %d agents\n", data.count);
+ return data.count;
+}
+
+static void kill_duplicate_offers(char *caller)
+{
+ unsigned long match_flags = MATCH_NO_REQUEST;
+ ao2_t_callback_data(cc_core_instances, OBJ_UNLINK | OBJ_NODATA, match_agent, caller, &match_flags, "Killing duplicate offers");
+}
+
+static void check_callback_sanity(const struct ast_cc_agent_callbacks *callbacks)
+{
+ ast_assert(callbacks->init != NULL);
+ ast_assert(callbacks->start_offer_timer != NULL);
+ ast_assert(callbacks->stop_offer_timer != NULL);
+ ast_assert(callbacks->ack != NULL);
+ ast_assert(callbacks->status_request != NULL);
+ ast_assert(callbacks->start_monitoring != NULL);
+ ast_assert(callbacks->callee_available != NULL);
+ ast_assert(callbacks->destructor != NULL);
+}
+
+static void agent_destroy(void *data)
+{
+ struct ast_cc_agent *agent = data;
+
+ if (agent->callbacks) {
+ agent->callbacks->destructor(agent);
+ }
+ ast_cc_config_params_destroy(agent->cc_params);
+}
+
+static struct ast_cc_agent *cc_agent_init(struct ast_channel *caller_chan,
+ const char * const caller_name, const int core_id,
+ struct cc_monitor_tree *interface_tree)
+{
+ struct ast_cc_agent *agent;
+ struct ast_cc_config_params *cc_params;
+
+ if (!(agent = ao2_t_alloc(sizeof(*agent) + strlen(caller_name), agent_destroy,
+ "Allocating new ast_cc_agent"))) {
+ return NULL;
+ }
+
+ agent->core_id = core_id;
+ strcpy(agent->device_name, caller_name);
+
+ cc_params = ast_channel_get_cc_config_params(caller_chan);
+ if (!cc_params) {
+ cc_unref(agent, "Could not get channel config params.");
+ return NULL;
+ }
+ if (!(agent->cc_params = ast_cc_config_params_init())) {
+ cc_unref(agent, "Could not init agent config params.");
+ return NULL;
+ }
+ ast_cc_copy_config_params(agent->cc_params, cc_params);
+
+ if (!(agent->callbacks = find_agent_callbacks(caller_chan))) {
+ cc_unref(agent, "Could not find agent callbacks.");
+ return NULL;
+ }
+ check_callback_sanity(agent->callbacks);
+
+ if (agent->callbacks->init(agent, caller_chan)) {
+ cc_unref(agent, "Agent init callback failed.");
+ return NULL;
+ }
+ ast_log_dynamic_level(cc_logger_level, "Core %d: Created an agent for caller %s\n",
+ agent->core_id, agent->device_name);
+ return agent;
+}
+
+/* Generic agent callbacks */
+static int cc_generic_agent_init(struct ast_cc_agent *agent, struct ast_channel *chan);
+static int cc_generic_agent_start_offer_timer(struct ast_cc_agent *agent);
+static int cc_generic_agent_stop_offer_timer(struct ast_cc_agent *agent);
+static void cc_generic_agent_ack(struct ast_cc_agent *agent);
+static int cc_generic_agent_status_request(struct ast_cc_agent *agent);
+static int cc_generic_agent_stop_ringing(struct ast_cc_agent *agent);
+static int cc_generic_agent_start_monitoring(struct ast_cc_agent *agent);
+static int cc_generic_agent_recall(struct ast_cc_agent *agent);
+static void cc_generic_agent_destructor(struct ast_cc_agent *agent);
+
+static struct ast_cc_agent_callbacks generic_agent_callbacks = {
+ .type = "generic",
+ .init = cc_generic_agent_init,
+ .start_offer_timer = cc_generic_agent_start_offer_timer,
+ .stop_offer_timer = cc_generic_agent_stop_offer_timer,
+ .ack = cc_generic_agent_ack,
+ .status_request = cc_generic_agent_status_request,
+ .stop_ringing = cc_generic_agent_stop_ringing,
+ .start_monitoring = cc_generic_agent_start_monitoring,
+ .callee_available = cc_generic_agent_recall,
+ .destructor = cc_generic_agent_destructor,
+};
+
+struct cc_generic_agent_pvt {
+ /*!
+ * Subscription to device state
+ *
+ * Used in the CC_CALLER_BUSY state. The
+ * generic agent will subscribe to the
+ * device state of the caller in order to
+ * determine when we may move on
+ */
+ struct ast_event_sub *sub;
+ /*!
+ * Scheduler id of offer timer.
+ */
+ int offer_timer_id;
+ /*!
+ * Caller ID number
+ *
+ * When we re-call the caller, we need
+ * to provide this information to
+ * ast_request_and_dial so that the
+ * information will be present in the
+ * call to the callee
+ */
+ char cid_num[AST_CHANNEL_NAME];
+ /*!
+ * Caller ID name
+ *
+ * See the description of cid_num.
+ * The same applies here, except this
+ * is the caller's name.
+ */
+ char cid_name[AST_CHANNEL_NAME];
+ /*!
+ * Extension dialed
+ *
+ * The original extension dialed. This is used
+ * so that when performing a recall, we can
+ * call the proper extension.
+ */
+ char exten[AST_CHANNEL_NAME];
+ /*!
+ * Context dialed
+ *
+ * The original context dialed. This is used
+ * so that when performaing a recall, we can
+ * call into the proper context
+ */
+ char context[AST_CHANNEL_NAME];
+};
+
+static int cc_generic_agent_init(struct ast_cc_agent *agent, struct ast_channel *chan)
+{
+ struct cc_generic_agent_pvt *generic_pvt = ast_calloc(1, sizeof(*generic_pvt));
+
+ if (!generic_pvt) {
+ return -1;
+ }
+
+ generic_pvt->offer_timer_id = -1;
+ ast_copy_string(generic_pvt->cid_num, chan->cid.cid_num, sizeof(generic_pvt->cid_num));
+ ast_copy_string(generic_pvt->cid_name, chan->cid.cid_name, sizeof(generic_pvt->cid_name));
+ ast_copy_string(generic_pvt->exten, S_OR(chan->macroexten, chan->exten), sizeof(generic_pvt->exten));
+ ast_copy_string(generic_pvt->context, S_OR(chan->macrocontext, chan->context), sizeof(generic_pvt->context));
+ agent->private_data = generic_pvt;
+ ast_set_flag(agent, AST_CC_AGENT_SKIP_OFFER);
+ return 0;
+}
+
+static int offer_timer_expire(const void *data)
+{
+ const struct ast_cc_agent *agent = data;
+ struct cc_generic_agent_pvt *agent_pvt = agent->private_data;
+ ast_log_dynamic_level(cc_logger_level, "Core %d: Queuing change request because offer timer has expired.\n",
+ agent->core_id);
+ agent_pvt->offer_timer_id = -1;
+ ast_cc_failed(agent->core_id, "Generic agent %s offer timer expired", agent->device_name);
+ cc_unref((struct ast_cc_agent *)agent, "Remove scheduler's reference to the agent");
+ return 0;
+}
+
+static int cc_generic_agent_start_offer_timer(struct ast_cc_agent *agent)
+{
+ int when;
+ int sched_id;
+ struct cc_generic_agent_pvt *generic_pvt = agent->private_data;
+
+ ast_assert(cc_sched_thread != NULL);
+ ast_assert(agent->cc_params != NULL);
+
+ when = ast_get_cc_offer_timer(agent->cc_params) * 1000;
+ ast_log_dynamic_level(cc_logger_level, "Core %d: About to schedule offer timer expiration for %d ms\n",
+ agent->core_id, when);
+ if ((sched_id = ast_sched_thread_add(cc_sched_thread, when, offer_timer_expire, cc_ref(agent, "Give scheduler an agent ref"))) == -1) {
+ return -1;
+ }
+ generic_pvt->offer_timer_id = sched_id;
+ return 0;
+}
+
+static int cc_generic_agent_stop_offer_timer(struct ast_cc_agent *agent)
+{
+ struct cc_generic_agent_pvt *generic_pvt = agent->private_data;
+
+ if (generic_pvt->offer_timer_id != -1) {
+ if (!ast_sched_thread_del(cc_sched_thread, generic_pvt->offer_timer_id)) {
+ cc_unref(agent, "Remove scheduler's reference to the agent");
+ }
+ generic_pvt->offer_timer_id = -1;
+ }
+ return 0;
+}
+
+static void cc_generic_agent_ack(struct ast_cc_agent *agent)
+{
+ /* The generic agent doesn't have to do anything special to
+ * acknowledge a CC request. Just return.
+ */
+ return;
+}
+
+static int cc_generic_agent_status_request(struct ast_cc_agent *agent)
+{
+ ast_cc_agent_status_response(agent->core_id, ast_device_state(agent->device_name));
+ return 0;
+}
+
+static int cc_generic_agent_stop_ringing(struct ast_cc_agent *agent)
+{
+ struct ast_channel *recall_chan = ast_channel_get_by_name_prefix(agent->device_name, strlen(agent->device_name));
+
+ if (!recall_chan) {
+ return 0;
+ }
+
+ ast_softhangup(recall_chan, AST_SOFTHANGUP_EXPLICIT);
+ return 0;
+}
+
+static int generic_agent_devstate_unsubscribe(void *data)
+{
+ struct ast_cc_agent *agent = data;
+ struct cc_generic_agent_pvt *generic_pvt = agent->private_data;
+
+ if (generic_pvt->sub != NULL) {
+ generic_pvt->sub = ast_event_unsubscribe(generic_pvt->sub);
+ }
+ cc_unref(agent, "Done unsubscribing from devstate");
+ return 0;
+}
+
+static void generic_agent_devstate_cb(const struct ast_event *event, void *userdata)
+{
+ struct ast_cc_agent *agent = userdata;
+
+ /* We can't unsubscribe from device state events here because it causes a deadlock */
+ if (ast_taskprocessor_push(cc_core_taskprocessor, generic_agent_devstate_unsubscribe,
+ cc_ref(agent, "ref agent for device state unsubscription"))) {
+ cc_unref(agent, "Unref agent unsubscribing from devstate failed");
+ }
+ ast_cc_agent_caller_available(agent->core_id, "%s is no longer busy", agent->device_name);
+}
+
+static int cc_generic_agent_start_monitoring(struct ast_cc_agent *agent)
+{
+ struct cc_generic_agent_pvt *generic_pvt = agent->private_data;
+ struct ast_str *str = ast_str_alloca(128);
+
+ ast_assert(generic_pvt->sub == NULL);
+ ast_str_set(&str, 0, "Starting to monitor %s device state since it is busy\n", agent->device_name);
+
+ if (!(generic_pvt->sub = ast_event_subscribe(
+ AST_EVENT_DEVICE_STATE, generic_agent_devstate_cb, ast_str_buffer(str), agent,
+ AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR, agent->device_name,
+ AST_EVENT_IE_STATE, AST_EVENT_IE_PLTYPE_UINT, AST_DEVICE_NOT_INUSE,
+ AST_EVENT_IE_END))) {
+ return -1;
+ }
+ return 0;
+}
+
+static void *generic_recall(void *data)
+{
+ struct ast_cc_agent *agent = data;
+ struct cc_generic_agent_pvt *generic_pvt = agent->private_data;
+ const char *interface = S_OR(ast_get_cc_agent_dialstring(agent->cc_params), ast_strdupa(agent->device_name));
+ const char *tech;
+ char *target;
+ int reason;
+ struct ast_channel *chan;
+ const char *callback_macro = ast_get_cc_callback_macro(agent->cc_params);
+ unsigned int recall_timer = ast_get_cc_recall_timer(agent->cc_params) * 1000;
+
+ tech = interface;
+ if ((target = strchr(interface, '/'))) {
+ *target++ = '\0';
+ }
+ if (!(chan = ast_request_and_dial(tech, AST_FORMAT_SLINEAR, NULL, target, recall_timer, &reason, generic_pvt->cid_num, generic_pvt->cid_name))) {
+ /* Hmm, no channel. Sucks for you, bud.
+ */
+ ast_log_dynamic_level(cc_logger_level, "Core %d: Failed to call back %s for reason %d\n",
+ agent->core_id, agent->device_name, reason);
+ ast_cc_failed(agent->core_id, "Failed to call back device %s/%s", tech, target);
+ return NULL;
+ }
+ if (!ast_strlen_zero(callback_macro)) {
+ ast_log_dynamic_level(cc_logger_level, "Core %d: There's a callback macro configured for agent %s\n",
+ agent->core_id, agent->device_name);
+ if (ast_app_run_macro(NULL, chan, callback_macro, NULL)) {
+ ast_cc_failed(agent->core_id, "Callback macro to %s failed. Maybe a hangup?", agent->device_name);
+ ast_hangup(chan);
+ return NULL;
+ }
+ }
+ /* We have a channel. It's time now to set up the datastore of recalled CC interfaces.
+ * This will be a common task for all recall functions. If it were possible, I'd have
+ * the core do it automatically, but alas I cannot. Instead, I will provide a public
+ * function to do so.
+ */
+ ast_setup_cc_recall_datastore(chan, agent->core_id);
+ ast_cc_agent_set_interfaces_chanvar(chan);
+
+ ast_copy_string(chan->exten, generic_pvt->exten, sizeof(chan->exten));
+ ast_copy_string(chan->context, generic_pvt->context, sizeof(chan->context));
+ chan->priority = 1;
+ ast_cc_agent_recalling(agent->core_id, "Generic agent %s is recalling", agent->device_name);
+ ast_pbx_start(chan);
+ return NULL;
+}
+
+static int cc_generic_agent_recall(struct ast_cc_agent *agent)
+{
+ pthread_t clotho;
+ enum ast_device_state current_state = ast_device_state(agent->device_name);
+
+ if (current_state != AST_DEVICE_NOT_INUSE && current_state != AST_DEVICE_UNKNOWN) {
+ /* We can't try to contact the device right now because he's not available
+ * Let the core know he's busy.
+ */
+ ast_cc_agent_caller_busy(agent->core_id, "Generic agent caller %s is busy", agent->device_name);
+ return 0;
+ }
+ ast_pthread_create_detached_background(&clotho, NULL, generic_recall, agent);
+ return 0;
+}
+
+static void cc_generic_agent_destructor(struct ast_cc_agent *agent)
+{
+ struct cc_generic_agent_pvt *agent_pvt = agent->private_data;
+
+ if (!agent_pvt) {
+ /* The agent constructor probably failed. */
+ return;
+ }
+
+ cc_generic_agent_stop_offer_timer(agent);
+ if (agent_pvt->sub) {
+ agent_pvt->sub = ast_event_unsubscribe(agent_pvt->sub);
+ }
+
+ ast_free(agent_pvt);
+}
+
+static void cc_core_instance_destructor(void *data)
+{
+ struct cc_core_instance *core_instance = data;
+ ast_log_dynamic_level(cc_logger_level, "Core %d: Destroying core instance\n", core_instance->core_id);
+ if (core_instance->agent) {
+ cc_unref(core_instance->agent, "Core instance is done with the agent now");
+ }
+ if (core_instance->monitors) {
+ core_instance->monitors = cc_unref(core_instance->monitors, "Core instance is done with interface list");
+ }
+}
+
+static struct cc_core_instance *cc_core_init_instance(struct ast_channel *caller_chan,
+ struct cc_monitor_tree *called_tree, const int core_id, struct cc_control_payload *cc_data)
+{
+ char caller[AST_CHANNEL_NAME];
+ struct cc_core_instance *core_instance;
+ struct ast_cc_config_params *cc_params;
+ long agent_count;
+ int recall_core_id;
+
+ ast_channel_get_device_name(caller_chan, caller, sizeof(caller));
+ cc_params = ast_channel_get_cc_config_params(caller_chan);
+ if (!cc_params) {
+ ast_log_dynamic_level(cc_logger_level, "Could not get CC parameters for %s\n",
+ caller);
+ return NULL;
+ }
+ /* First, we need to kill off other pending CC offers from caller. If the caller is going
+ * to request a CC service, it may only be for the latest call he made.
+ */
+ if (ast_get_cc_agent_policy(cc_params) == AST_CC_AGENT_GENERIC) {
+ kill_duplicate_offers(caller);
+ }
+
+ ast_cc_is_recall(caller_chan, &recall_core_id, NULL);
+ agent_count = count_agents(caller, recall_core_id);
+ if (agent_count >= ast_get_cc_max_agents(cc_params)) {
+ ast_log_dynamic_level(cc_logger_level, "Caller %s already has the maximum number of agents configured\n", caller);
+ return NULL;
+ }
+
+ /* Generic agents can only have a single outstanding CC request per caller. */
+ if (agent_count > 0 && ast_get_cc_agent_policy(cc_params) == AST_CC_AGENT_GENERIC) {
+ ast_log_dynamic_level(cc_logger_level, "Generic agents can only have a single outstanding request\n");
+ return NULL;
+ }
+
+ /* Next, we need to create the core instance for this call */
+ if (!(core_instance = ao2_t_alloc(sizeof(*core_instance), cc_core_instance_destructor, "Creating core instance for CC"))) {
+ return NULL;
+ }
+
+ core_instance->core_id = core_id;
+ if (!(core_instance->agent = cc_agent_init(caller_chan, caller, core_instance->core_id, called_tree))) {
+ cc_unref(core_instance, "Couldn't allocate agent, unref core_instance");
+ return NULL;
+ }
+
+ core_instance->monitors = cc_ref(called_tree, "Core instance getting ref to monitor tree");
+
+ ao2_t_link(cc_core_instances, core_instance, "Link core instance into container");
+
+ return core_instance;
+}
+
+struct cc_state_change_args {
+ enum cc_state state;
+ int core_id;
+ char debug[1];
+};
+
+static int is_state_change_valid(enum cc_state current_state, const enum cc_state new_state, struct ast_cc_agent *agent)
+{
+ int is_valid = 0;
+ switch (new_state) {
+ case CC_AVAILABLE:
+ ast_log_dynamic_level(cc_logger_level, "Core %d: Asked to change to state %d? That should never happen.\n",
+ agent->core_id, new_state);
+ break;
+ case CC_CALLER_OFFERED:
+ if (current_state == CC_AVAILABLE) {
+ is_valid = 1;
+ }
+ break;
+ case CC_CALLER_REQUESTED:
+ if (current_state == CC_CALLER_OFFERED ||
+ (current_state == CC_AVAILABLE && ast_test_flag(agent, AST_CC_AGENT_SKIP_OFFER))) {
+ is_valid = 1;
+ }
+ break;
+ case CC_ACTIVE:
+ if (current_state == CC_CALLER_REQUESTED || current_state == CC_CALLER_BUSY) {
+ is_valid = 1;
+ }
+ break;
+ case CC_CALLEE_READY:
+ if (current_state == CC_ACTIVE) {
+ is_valid = 1;
+ }
+ break;
+ case CC_CALLER_BUSY:
+ if (current_state == CC_CALLEE_READY) {
+ is_valid = 1;
+ }
+ break;
+ case CC_RECALLING:
+ if (current_state == CC_CALLEE_READY) {
+ is_valid = 1;
+ }
+ break;
+ case CC_COMPLETE:
+ if (current_state == CC_RECALLING) {
+ is_valid = 1;
+ }
+ break;
+ case CC_FAILED:
+ is_valid = 1;
+ break;
+ default:
+ ast_log_dynamic_level(cc_logger_level, "Core %d: Asked to change to unknown state %d\n",
+ agent->core_id, new_state);
+ break;
+ }
+
+ return is_valid;
+}
+
+static int cc_available(struct cc_core_instance *core_instance, struct cc_state_change_args *args, enum cc_state previous_state)
+{
+ /* This should never happen... */
+ ast_log(LOG_WARNING, "Someone requested to change to CC_AVAILABLE? Ignoring.\n");
+ return -1;
+}
+
+static int cc_caller_offered(struct cc_core_instance *core_instance, struct cc_state_change_args *args, enum cc_state previous_state)
+{
+ if (core_instance->agent->callbacks->start_offer_timer(core_instance->agent)) {
+ ast_cc_failed(core_instance->core_id, "Failed to start the offer timer for %s\n",
+ core_instance->agent->device_name);
+ return -1;
+ }
+ manager_event(EVENT_FLAG_CC, "CCOfferTimerStart",
+ "CoreID: %d\r\n"
+ "Caller: %s\r\n"
+ "Expires: %u\r\n",
+ core_instance->core_id, core_instance->agent->device_name, core_instance->agent->cc_params->cc_offer_timer);
+ ast_log_dynamic_level(cc_logger_level, "Core %d: Started the offer timer for the agent %s!\n",
+ core_instance->core_id, core_instance->agent->device_name);
+ return 0;
+}
+
+/*!
+ * \brief check if the core instance has any device monitors
+ *
+ * In any case where we end up removing a device monitor from the
+ * list of device monitors, it is important to see what the state
+ * of the list is afterwards. If we find that we only have extension
+ * monitors left, then no devices are actually being monitored.
+ * In such a case, we need to declare that CC has failed for this
+ * call. This function helps those cases to determine if they should
+ * declare failure.
+ *
+ * \param core_instance The core instance we are checking for the existence
+ * of device monitors
+ * \retval 0 No device monitors exist on this core_instance
+ * \retval 1 There is still at least 1 device monitor remaining
+ */
+static int has_device_monitors(struct cc_core_instance *core_instance)
+{
+ struct ast_cc_monitor *iter;
+ int res = 0;
+
+ AST_LIST_TRAVERSE(core_instance->monitors, iter, next) {
+ if (iter->interface->monitor_class == AST_CC_DEVICE_MONITOR) {
+ res = 1;
+ break;
+ }
+ }
+
+ return res;
+}
+
+static void request_cc(struct cc_core_instance *core_instance)
+{
+ struct ast_cc_monitor *monitor_iter;
+ AST_LIST_LOCK(core_instance->monitors);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(core_instance->monitors, monitor_iter, next) {
+ if (monitor_iter->interface->monitor_class == AST_CC_DEVICE_MONITOR) {
+ if (monitor_iter->callbacks->request_cc(monitor_iter, &monitor_iter->available_timer_id)) {
+ AST_LIST_REMOVE_CURRENT(next);
+ cc_extension_monitor_change_is_valid(core_instance, monitor_iter->parent_id,
+ monitor_iter->interface->device_name, 1);
+ cc_unref(monitor_iter, "request_cc failed. Unref list's reference to monitor");
+ } else {
+ manager_event(EVENT_FLAG_CC, "CCRequested",
+ "CoreID: %d\r\n"
+ "Caller: %s\r\n"
+ "Callee: %s\r\n",
+ core_instance->core_id, core_instance->agent->device_name, monitor_iter->interface->device_name);
+ }
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+
+ if (!has_device_monitors(core_instance)) {
+ ast_cc_failed(core_instance->core_id, "All device monitors failed to request CC");
+ }
+ AST_LIST_UNLOCK(core_instance->monitors);
+}
+
+static int cc_caller_requested(struct cc_core_instance *core_instance, struct cc_state_change_args *args, enum cc_state previous_state)
+{
+ if (!ast_cc_request_is_within_limits()) {
+ ast_log(LOG_WARNING, "Cannot request CC since there is no more room for requests\n");
+ ast_cc_failed(core_instance->core_id, "Too many requests in the system");
+ return -1;
+ }
+ core_instance->agent->callbacks->stop_offer_timer(core_instance->agent);
+ request_cc(core_instance);
+ return 0;
+}
+
+static void unsuspend(struct cc_core_instance *core_instance)
+{
+ struct ast_cc_monitor *monitor_iter;
+ AST_LIST_LOCK(core_instance->monitors);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(core_instance->monitors, monitor_iter, next) {
+ if (monitor_iter->interface->monitor_class == AST_CC_DEVICE_MONITOR) {
+ if (monitor_iter->callbacks->unsuspend(monitor_iter)) {
+ AST_LIST_REMOVE_CURRENT(next);
+ cc_extension_monitor_change_is_valid(core_instance, monitor_iter->parent_id,
+ monitor_iter->interface->device_name, 1);
+ cc_unref(monitor_iter, "unsuspend failed. Unref list's reference to monitor");
+ }
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+
+ if (!has_device_monitors(core_instance)) {
+ ast_cc_failed(core_instance->core_id, "All device monitors failed to unsuspend CC");
+ }
+ AST_LIST_UNLOCK(core_instance->monitors);
+}
+
+static int cc_active(struct cc_core_instance *core_instance, struct cc_state_change_args *args, enum cc_state previous_state)
+{
+ /* Either
+ * 1. Callee accepted CC request, call agent's ack callback.
+ * 2. Caller became available, call agent's stop_monitoring callback and
+ * call monitor's unsuspend callback.
+ */
+ if (previous_state == CC_CALLER_REQUESTED) {
+ core_instance->agent->callbacks->ack(core_instance->agent);
+ manager_event(EVENT_FLAG_CC, "CCRequestAcknowledged",
+ "CoreID: %d\r\n"
+ "Caller: %s\r\n",
+ core_instance->core_id, core_instance->agent->device_name);
+ } else if (previous_state == CC_CALLER_BUSY) {
+ manager_event(EVENT_FLAG_CC, "CCCallerStopMonitoring",
+ "CoreID: %d\r\n"
+ "Caller: %s\r\n",
+ core_instance->core_id, core_instance->agent->device_name);
+ unsuspend(core_instance);
+ }
+ /* Not possible for previous_state to be anything else due to the is_state_change_valid check at the beginning */
+ return 0;
+}
+
+static int cc_callee_ready(struct cc_core_instance *core_instance, struct cc_state_change_args *args, enum cc_state previous_state)
+{
+ core_instance->agent->callbacks->callee_available(core_instance->agent);
+ return 0;
+}
+
+static void suspend(struct cc_core_instance *core_instance)
+{
+ struct ast_cc_monitor *monitor_iter;
+ AST_LIST_LOCK(core_instance->monitors);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(core_instance->monitors, monitor_iter, next) {
+ if (monitor_iter->interface->monitor_class == AST_CC_DEVICE_MONITOR) {
+ if (monitor_iter->callbacks->suspend(monitor_iter)) {
+ AST_LIST_REMOVE_CURRENT(next);
+ cc_extension_monitor_change_is_valid(core_instance, monitor_iter->parent_id,
+ monitor_iter->interface->device_name, 1);
+ cc_unref(monitor_iter, "suspend failed. Unref list's reference to monitor");
+ }
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+
+ if (!has_device_monitors(core_instance)) {
+ ast_cc_failed(core_instance->core_id, "All device monitors failed to suspend CC");
+ }
+ AST_LIST_UNLOCK(core_instance->monitors);
+}
+
+static int cc_caller_busy(struct cc_core_instance *core_instance, struct cc_state_change_args *args, enum cc_state previous_state)
+{
+ /* Callee was available, but caller was busy, call agent's begin_monitoring callback
+ * and call monitor's suspend callback.
+ */
+ suspend(core_instance);
+ core_instance->agent->callbacks->start_monitoring(core_instance->agent);
+ manager_event(EVENT_FLAG_CC, "CCCallerStartMonitoring",
+ "CoreID: %d\r\n"
+ "Caller: %s\r\n",
+ core_instance->core_id, core_instance->agent->device_name);
+ return 0;
+}
+
+static void cancel_available_timer(struct cc_core_instance *core_instance)
+{
+ struct ast_cc_monitor *monitor_iter;
+ AST_LIST_LOCK(core_instance->monitors);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(core_instance->monitors, monitor_iter, next) {
+ if (monitor_iter->interface->monitor_class == AST_CC_DEVICE_MONITOR) {
+ if (monitor_iter->callbacks->cancel_available_timer(monitor_iter, &monitor_iter->available_timer_id)) {
+ AST_LIST_REMOVE_CURRENT(next);
+ cc_extension_monitor_change_is_valid(core_instance, monitor_iter->parent_id,
+ monitor_iter->interface->device_name, 1);
+ cc_unref(monitor_iter, "cancel_available_timer failed. Unref list's reference to monitor");
+ }
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+
+ if (!has_device_monitors(core_instance)) {
+ ast_cc_failed(core_instance->core_id, "All device monitors failed to cancel their available timers");
+ }
+ AST_LIST_UNLOCK(core_instance->monitors);
+}
+
+static int cc_recalling(struct cc_core_instance *core_instance, struct cc_state_change_args *args, enum cc_state previous_state)
+{
+ /* Both caller and callee are available, call agent's recall callback
+ */
+ cancel_available_timer(core_instance);
+ manager_event(EVENT_FLAG_CC, "CCCallerRecalling",
+ "CoreID: %d\r\n"
+ "Caller: %s\r\n",
+ core_instance->core_id, core_instance->agent->device_name);
+ return 0;
+}
+
+static int cc_complete(struct cc_core_instance *core_instance, struct cc_state_change_args *args, enum cc_state previous_state)
+{
+ /* Recall has made progress, call agent and monitor destructor functions
+ */
+ manager_event(EVENT_FLAG_CC, "CCRecallComplete",
+ "CoreID: %d\r\n"
+ "Caller: %s\r\n",
+ core_instance->core_id, core_instance->agent->device_name);
+ ao2_t_unlink(cc_core_instances, core_instance, "Unlink core instance since CC recall has completed");
+ return 0;
+}
+
+static int cc_failed(struct cc_core_instance *core_instance, struct cc_state_change_args *args, enum cc_state previous_state)
+{
+ /* Something along the way failed, call agent and monitor destructor functions
+ */
+ manager_event(EVENT_FLAG_CC, "CCFailure",
+ "CoreID: %d\r\n"
+ "Caller: %s\r\n"
+ "Reason: %s\r\n",
+ core_instance->core_id, core_instance->agent->device_name, args->debug);
+ ao2_t_unlink(cc_core_instances, core_instance, "Unlink core instance since CC failed");
+ return 0;
+}
+
+static int (* const state_change_funcs [])(struct cc_core_instance *, struct cc_state_change_args *, enum cc_state previous_state) = {
+ [CC_AVAILABLE] = cc_available,
+ [CC_CALLER_OFFERED] = cc_caller_offered,
+ [CC_CALLER_REQUESTED] = cc_caller_requested,
+ [CC_ACTIVE] = cc_active,
+ [CC_CALLEE_READY] = cc_callee_ready,
+ [CC_CALLER_BUSY] = cc_caller_busy,
+ [CC_RECALLING] = cc_recalling,
+ [CC_COMPLETE] = cc_complete,
+ [CC_FAILED] = cc_failed,
+};
+
+static int cc_do_state_change(void *datap)
+{
+ struct cc_state_change_args *args = datap;
+ struct cc_core_instance *core_instance;
+ enum cc_state previous_state;
+ int res;
+
+ ast_log_dynamic_level(cc_logger_level, "Core %d: State change to %d requested. Reason: %s\n",
+ args->core_id, args->state, args->debug);
+
+ if (!(core_instance = find_cc_core_instance(args->core_id))) {
+ ast_log_dynamic_level(cc_logger_level, "Core %d: Unable to find core instance.\n", args->core_id);
+ ast_free(args);
+ return -1;
+ }
+
+ if (!is_state_change_valid(core_instance->current_state, args->state, core_instance->agent)) {
+ ast_log_dynamic_level(cc_logger_level, "Core %d: Invalid state change requested. Cannot go from %s to %s\n",
+ args->core_id, cc_state_to_string(core_instance->current_state), cc_state_to_string(args->state));
+ ast_free(args);
+ cc_unref(core_instance, "Unref core instance from when it was found earlier");
+ return -1;
+ }
+
+ /* We can change to the new state now. */
+ previous_state = core_instance->current_state;
+ core_instance->current_state = args->state;
+ res = state_change_funcs[core_instance->current_state](core_instance, args, previous_state);
+
+ ast_free(args);
+ cc_unref(core_instance, "Unref since state change has completed"); /* From ao2_find */
+ return res;
+}
+
+static int cc_request_state_change(enum cc_state state, const int core_id, const char *debug, va_list ap)
+{
+ int res;
+ int debuglen;
+ char dummy[1];
+ va_list aq;
+ struct cc_state_change_args *args;
+ /* This initial call to vsnprintf is simply to find what the
+ * size of the string needs to be
+ */
+ va_copy(aq, ap);
+ /* We add 1 to the result since vsnprintf's return does not
+ * include the terminating null byte
+ */
+ debuglen = vsnprintf(dummy, sizeof(dummy), debug, aq) + 1;
+ va_end(aq);
+
+ if (!(args = ast_calloc(1, sizeof(*args) + debuglen))) {
+ return -1;
+ }
+
+ args->state = state;
+ args->core_id = core_id;
+ vsnprintf(args->debug, debuglen, debug, ap);
+
+ res = ast_taskprocessor_push(cc_core_taskprocessor, cc_do_state_change, args);
+ if (res) {
+ ast_free(args);
+ }
+ return res;
+}
+
+struct cc_recall_ds_data {
+ int core_id;
+ char ignore;
+ char nested;
+ struct cc_monitor_tree *interface_tree;
+};
+
+static void *cc_recall_ds_duplicate(void *data)
+{
+ struct cc_recall_ds_data *old_data = data;
+ struct cc_recall_ds_data *new_data = ast_calloc(1, sizeof(*new_data));
+
+ if (!new_data) {
+ return NULL;
+ }
+ new_data->interface_tree = cc_ref(old_data->interface_tree, "Bump refcount of monitor tree for recall datastore duplicate");
+ new_data->core_id = old_data->core_id;
+ new_data->nested = 1;
+ return new_data;
+}
+
+static void cc_recall_ds_destroy(void *data)
+{
+ struct cc_recall_ds_data *recall_data = data;
+ recall_data->interface_tree = cc_unref(recall_data->interface_tree, "Unref recall monitor tree");
+ ast_free(recall_data);
+}
+
+static struct ast_datastore_info recall_ds_info = {
+ .type = "cc_recall",
+ .duplicate = cc_recall_ds_duplicate,
+ .destroy = cc_recall_ds_destroy,
+};
+
+int ast_setup_cc_recall_datastore(struct ast_channel *chan, const int core_id)
+{
+ struct ast_datastore *recall_datastore = ast_datastore_alloc(&recall_ds_info, NULL);
+ struct cc_recall_ds_data *recall_data;
+ struct cc_core_instance *core_instance;
+
+ if (!recall_datastore) {
+ return -1;
+ }
+
+ if (!(recall_data = ast_calloc(1, sizeof(*recall_data)))) {
+ ast_datastore_free(recall_datastore);
+ return -1;
+ }
+
+ if (!(core_instance = find_cc_core_instance(core_id))) {
+ ast_free(recall_data);
+ ast_datastore_free(recall_datastore);
+ return -1;
+ }
+
+ recall_data->interface_tree = cc_ref(core_instance->monitors,
+ "Bump refcount for monitor tree for recall datastore");
+ recall_data->core_id = core_id;
+ recall_datastore->data = recall_data;
+ recall_datastore->inheritance = DATASTORE_INHERIT_FOREVER;
+ ast_channel_lock(chan);
+ ast_channel_datastore_add(chan, recall_datastore);
+ ast_channel_unlock(chan);
+ cc_unref(core_instance, "Recall datastore set up. No need for core_instance ref");
+ return 0;
+}
+
+int ast_cc_is_recall(struct ast_channel *chan, int *core_id, const char * const monitor_type)
+{
+ struct ast_datastore *recall_datastore;
+ struct cc_recall_ds_data *recall_data;
+ struct cc_monitor_tree *interface_tree;
+ char device_name[AST_CHANNEL_NAME];
+ struct ast_cc_monitor *device_monitor;
+ int core_id_candidate;
+
+ ast_assert(core_id != NULL);
+
+ *core_id = -1;
+
+ ast_channel_lock(chan);
+ if (!(recall_datastore = ast_channel_datastore_find(chan, &recall_ds_info, NULL))) {
+ /* Obviously not a recall if the datastore isn't present */
+ ast_channel_unlock(chan);
+ return 0;
+ }
+
+ recall_data = recall_datastore->data;
+
+ if (recall_data->ignore) {
+ /* Though this is a recall, the call to this particular interface is not part of the
+ * recall either because this is a call forward or because this is not the first
+ * invocation of Dial during this call
+ */
+ ast_channel_unlock(chan);
+ return 0;
+ }
+
+ if (!recall_data->nested) {
+ /* If the nested flag is not set, then this means that
+ * the channel passed to this function is the caller making
+ * the recall. This means that we shouldn't look through
+ * the monitor tree for the channel because it shouldn't be
+ * there. However, this is a recall though, so return true.
+ */
+ *core_id = recall_data->core_id;
+ ast_channel_unlock(chan);
+ return 1;
+ }
+
+ if (ast_strlen_zero(monitor_type)) {
+ /* If someone passed a NULL or empty monitor type, then it is clear
+ * the channel they passed in was an incoming channel, and so searching
+ * the list of dialed interfaces is not going to be helpful. Just return
+ * false immediately.
+ */
+ ast_channel_unlock(chan);
+ return 0;
+ }
+
+ interface_tree = recall_data->interface_tree;
+ ast_channel_get_device_name(chan, device_name, sizeof(device_name));
+ /* We grab the value of the recall_data->core_id so that we
+ * can unlock the channel before we start looking through the
+ * interface list. That way we don't have to worry about a possible
+ * clash between the channel lock and the monitor tree lock.
+ */
+ core_id_candidate = recall_data->core_id;
+ ast_channel_unlock(chan);
+
+ /*
+ * Now we need to find out if the channel device name
+ * is in the list of interfaces in the called tree.
+ */
+ AST_LIST_LOCK(interface_tree);
+ AST_LIST_TRAVERSE(interface_tree, device_monitor, next) {
+ if (!strcmp(device_monitor->interface->device_name, device_name) &&
+ !strcmp(device_monitor->interface->monitor_type, monitor_type)) {
+ /* BOOM! Device is in the tree! We have a winner! */
+ *core_id = core_id_candidate;
+ AST_LIST_UNLOCK(interface_tree);
+ return 1;
+ }
+ }
+ AST_LIST_UNLOCK(interface_tree);
+ return 0;
+}
+
+struct ast_cc_monitor *ast_cc_get_monitor_by_recall_core_id(const int core_id, const char * const device_name)
+{
+ struct cc_core_instance *core_instance = find_cc_core_instance(core_id);
+ struct ast_cc_monitor *monitor_iter;
+
+ if (!core_instance) {
+ return NULL;
+ }
+
+ AST_LIST_LOCK(core_instance->monitors);
+ AST_LIST_TRAVERSE(core_instance->monitors, monitor_iter, next) {
+ if (!strcmp(monitor_iter->interface->device_name, device_name)) {
+ /* Found a monitor. */
+ cc_ref(monitor_iter, "Hand the requester of the monitor a reference");
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(core_instance->monitors);
+ cc_unref(core_instance, "Done with core instance ref in ast_cc_get_monitor_by_recall_core_id");
+ return monitor_iter;
+}
+
+/*!
+ * \internal
+ * \brief uniquely append a dialstring to our CC_INTERFACES chanvar string.
+ *
+ * We will only append a string if it has not already appeared in our channel
+ * variable earlier. We ensure that we don't erroneously match substrings by
+ * adding an ampersand to the end of our potential dialstring and searching for
+ * it plus the ampersand in our variable.
+ *
+ * It's important to note that once we have built the full CC_INTERFACES string,
+ * there will be an extra ampersand at the end which must be stripped off by
+ * the caller of this function.
+ *
+ * \param str An ast_str holding what we will add to CC_INTERFACES
+ * \param dialstring A new dialstring to add
+ * \retval void
+ */
+static void cc_unique_append(struct ast_str *str, const char * const dialstring)
+{
+ char dialstring_search[AST_CHANNEL_NAME];
+
+ snprintf(dialstring_search, sizeof(dialstring_search), "%s%c", dialstring, '&');
+ if (strstr(ast_str_buffer(str), dialstring_search)) {
+ return;
+ }
+ ast_str_append(&str, 0, "%s", dialstring_search);
+}
+
+/*!
+ * \internal
+ * \brief Build the CC_INTERFACES channel variable
+ *
+ * The method used is to traverse the child dialstrings in the
+ * passed-in extension monitor, adding any that have the is_valid
+ * flag set. Then, traverse the monitors, finding all children
+ * of the starting extension monitor and adding their dialstrings
+ * as well.
+ *
+ * \param starting_point The extension monitor that is the parent to all
+ * monitors whose dialstrings should be added to CC_INTERFACES
+ * \param str Where we will store CC_INTERFACES
+ * \retval void
+ */
+static void build_cc_interfaces_chanvar(struct ast_cc_monitor *starting_point, struct ast_str *str)
+{
+ struct extension_monitor_pvt *extension_pvt;
+ struct extension_child_dialstring *child_dialstring;
+ struct ast_cc_monitor *monitor_iter = starting_point;
+ int top_level_id = starting_point->id;
+
+ /* First we need to take all of the is_valid child_dialstrings from
+ * the extension monitor we found and add them to the CC_INTERFACES
+ * chanvar
+ */
+ extension_pvt = starting_point->private_data;
+ AST_LIST_TRAVERSE(&extension_pvt->child_dialstrings, child_dialstring, next) {
+ if (child_dialstring->is_valid) {
+ cc_unique_append(str, child_dialstring->original_dialstring);
+ }
+ }
+
+ /* And now we get the dialstrings from each of the device monitors */
+ while ((monitor_iter = AST_LIST_NEXT(monitor_iter, next))) {
+ if (monitor_iter->parent_id == top_level_id) {
+ cc_unique_append(str, monitor_iter->dialstring);
+ }
+ }
+
+ /* str will have an extra '&' tacked onto the end of it, so we need
+ * to get rid of that.
+ */
+ ast_str_truncate(str, ast_str_strlen(str) - 1);
+}
+
+int ast_cc_agent_set_interfaces_chanvar(struct ast_channel *chan)
+{
+ struct ast_datastore *recall_datastore;
+ struct cc_monitor_tree *interface_tree;
+ struct ast_cc_monitor *monitor;
+ struct cc_recall_ds_data *recall_data;
+ struct ast_str *str = ast_str_create(64);
+ int core_id;
+
+ if (!str) {
+ return -1;
+ }
+
+ ast_channel_lock(chan);
+ if (!(recall_datastore = ast_channel_datastore_find(chan, &recall_ds_info, NULL))) {
+ ast_channel_unlock(chan);
+ ast_free(str);
+ return -1;
+ }
+ recall_data = recall_datastore->data;
+ interface_tree = recall_data->interface_tree;
+ core_id = recall_data->core_id;
+ ast_channel_unlock(chan);
+
+ AST_LIST_LOCK(interface_tree);
+ monitor = AST_LIST_FIRST(interface_tree);
+ build_cc_interfaces_chanvar(monitor, str);
+ AST_LIST_UNLOCK(interface_tree);
+
+ pbx_builtin_setvar_helper(chan, "CC_INTERFACES", ast_str_buffer(str));
+ ast_log_dynamic_level(cc_logger_level, "Core %d: CC_INTERFACES set to %s\n",
+ core_id, ast_str_buffer(str));
+
+ ast_free(str);
+ return 0;
+}
+
+int ast_set_cc_interfaces_chanvar(struct ast_channel *chan, const char * const extension)
+{
+ struct ast_datastore *recall_datastore;
+ struct cc_monitor_tree *interface_tree;
+ struct ast_cc_monitor *monitor_iter;
+ struct cc_recall_ds_data *recall_data;
+ struct ast_str *str = ast_str_create(64);
+ int core_id;
+
+ if (!str) {
+ return -1;
+ }
+
+ ast_channel_lock(chan);
+ if (!(recall_datastore = ast_channel_datastore_find(chan, &recall_ds_info, NULL))) {
+ ast_channel_unlock(chan);
+ ast_free(str);
+ return -1;
+ }
+ recall_data = recall_datastore->data;
+ interface_tree = recall_data->interface_tree;
+ core_id = recall_data->core_id;
+ ast_channel_unlock(chan);
+
+ AST_LIST_LOCK(interface_tree);
+ AST_LIST_TRAVERSE(interface_tree, monitor_iter, next) {
+ if (!strcmp(monitor_iter->interface->device_name, extension)) {
+ break;
+ }
+ }
+
+ if (!monitor_iter) {
+ /* We couldn't find this extension. This may be because
+ * we have been directed into an unexpected extension because
+ * the admin has changed a CC_INTERFACES variable at some point.
+ */
+ AST_LIST_UNLOCK(interface_tree);
+ ast_free(str);
+ return -1;
+ }
+
+ build_cc_interfaces_chanvar(monitor_iter, str);
+ AST_LIST_UNLOCK(interface_tree);
+
+ pbx_builtin_setvar_helper(chan, "CC_INTERFACES", ast_str_buffer(str));
+ ast_log_dynamic_level(cc_logger_level, "Core %d: CC_INTERFACES set to %s\n",
+ core_id, ast_str_buffer(str));
+
+ ast_free(str);
+ return 0;
+}
+
+void ast_ignore_cc(struct ast_channel *chan)
+{
+ struct ast_datastore *cc_datastore;
+ struct ast_datastore *cc_recall_datastore;
+ struct dialed_cc_interfaces *cc_interfaces;
+ struct cc_recall_ds_data *recall_cc_data;
+
+ ast_channel_lock(chan);
+ if ((cc_datastore = ast_channel_datastore_find(chan, &dialed_cc_interfaces_info, NULL))) {
+ cc_interfaces = cc_datastore->data;
+ cc_interfaces->ignore = 1;
+ }
+
+ if ((cc_recall_datastore = ast_channel_datastore_find(chan, &recall_ds_info, NULL))) {
+ recall_cc_data = cc_recall_datastore->data;
+ recall_cc_data->ignore = 1;
+ }
+ ast_channel_unlock(chan);
+}
+
+static __attribute__((format(printf, 2, 3))) int cc_offer(const int core_id, const char * const debug, ...)
+{
+ va_list ap;
+ int res;
+
+ va_start(ap, debug);
+ res = cc_request_state_change(CC_CALLER_OFFERED, core_id, debug, ap);
+ va_end(ap);
+ return res;
+}
+
+int ast_cc_offer(struct ast_channel *caller_chan)
+{
+ int core_id;
+ int res = -1;
+ struct ast_datastore *datastore;
+ struct dialed_cc_interfaces *cc_interfaces;
+ char cc_is_offerable;
+
+ ast_channel_lock(caller_chan);
+ if (!(datastore = ast_channel_datastore_find(caller_chan, &dialed_cc_interfaces_info, NULL))) {
+ ast_channel_unlock(caller_chan);
+ return res;
+ }
+
+ cc_interfaces = datastore->data;
+ cc_is_offerable = cc_interfaces->is_original_caller;
+ core_id = cc_interfaces->core_id;
+ ast_channel_unlock(caller_chan);
+
+ if (cc_is_offerable) {
+ res = cc_offer(core_id, "CC offered to caller %s", caller_chan->name);
+ }
+ return res;
+}
+
+int ast_cc_agent_accept_request(int core_id, const char * const debug, ...)
+{
+ va_list ap;
+ int res;
+
+ va_start(ap, debug);
+ res = cc_request_state_change(CC_CALLER_REQUESTED, core_id, debug, ap);
+ va_end(ap);
+ return res;
+}
+
+int ast_cc_monitor_request_acked(int core_id, const char * const debug, ...)
+{
+ va_list ap;
+ int res;
+
+ va_start(ap, debug);
+ res = cc_request_state_change(CC_ACTIVE, core_id, debug, ap);
+ va_end(ap);
+ return res;
+}
+
+int ast_cc_monitor_callee_available(const int core_id, const char * const debug, ...)
+{
+ va_list ap;
+ int res;
+
+ va_start(ap, debug);
+ res = cc_request_state_change(CC_CALLEE_READY, core_id, debug, ap);
+ va_end(ap);
+ return res;
+}
+
+int ast_cc_agent_caller_busy(int core_id, const char * debug, ...)
+{
+ va_list ap;
+ int res;
+
+ va_start(ap, debug);
+ res = cc_request_state_change(CC_CALLER_BUSY, core_id, debug, ap);
+ va_end(ap);
+ return res;
+}
+
+int ast_cc_agent_caller_available(int core_id, const char * const debug, ...)
+{
+ va_list ap;
+ int res;
+
+ va_start(ap, debug);
+ res = cc_request_state_change(CC_ACTIVE, core_id, debug, ap);
+ va_end(ap);
+ return res;
+}
+
+int ast_cc_agent_recalling(int core_id, const char * const debug, ...)
+{
+ va_list ap;
+ int res;
+
+ va_start(ap, debug);
+ res = cc_request_state_change(CC_RECALLING, core_id, debug, ap);
+ va_end(ap);
+ return res;
+}
+
+int ast_cc_completed(struct ast_channel *chan, const char * const debug, ...)
+{
+ struct ast_datastore *recall_datastore;
+ struct cc_recall_ds_data *recall_data;
+ int core_id;
+ va_list ap;
+ int res;
+
+ ast_channel_lock(chan);
+ if (!(recall_datastore = ast_channel_datastore_find(chan, &recall_ds_info, NULL))) {
+ /* Silly! Why did you call this function if there's no recall DS? */
+ ast_channel_unlock(chan);
+ return -1;
+ }
+ recall_data = recall_datastore->data;
+ if (recall_data->nested || recall_data->ignore) {
+ /* If this is being called from a nested Dial, it is too
+ * early to determine if the recall has actually completed.
+ * The outermost dial is the only one with the authority to
+ * declare the recall to be complete.
+ *
+ * Similarly, if this function has been called when the
+ * recall has progressed beyond the first dial, this is not
+ * a legitimate time to declare the recall to be done. In fact,
+ * that should have been done already.
+ */
+ ast_channel_unlock(chan);
+ return -1;
+ }
+ core_id = recall_data->core_id;
+ ast_channel_unlock(chan);
+ va_start(ap, debug);
+ res = cc_request_state_change(CC_COMPLETE, core_id, debug, ap);
+ va_end(ap);
+ return res;
+}
+
+int ast_cc_failed(int core_id, const char * const debug, ...)
+{
+ va_list ap;
+ int res;
+
+ va_start(ap, debug);
+ res = cc_request_state_change(CC_FAILED, core_id, debug, ap);
+ va_end(ap);
+ return res;
+}
+
+struct ast_cc_monitor_failure_data {
+ const char *device_name;
+ char *debug;
+ int core_id;
+};
+
+static int cc_monitor_failed(void *data)
+{
+ struct ast_cc_monitor_failure_data *failure_data = data;
+ struct cc_core_instance *core_instance;
+ struct ast_cc_monitor *monitor_iter;
+
+ core_instance = find_cc_core_instance(failure_data->core_id);
+ if (!core_instance) {
+ /* Core instance no longer exists or invalid core_id. */
+ ast_log_dynamic_level(cc_logger_level,
+ "Core %d: Could not find core instance for device %s '%s'\n",
+ failure_data->core_id, failure_data->device_name, failure_data->debug);
+ ast_free((char *) failure_data->device_name);
+ ast_free((char *) failure_data->debug);
+ ast_free(failure_data);
+ return -1;
+ }
+
+ AST_LIST_LOCK(core_instance->monitors);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(core_instance->monitors, monitor_iter, next) {
+ if (monitor_iter->interface->monitor_class == AST_CC_DEVICE_MONITOR) {
+ if (!strcmp(monitor_iter->interface->device_name, failure_data->device_name)) {
+ AST_LIST_REMOVE_CURRENT(next);
+ cc_extension_monitor_change_is_valid(core_instance, monitor_iter->parent_id,
+ monitor_iter->interface->device_name, 1);
+ monitor_iter->callbacks->cancel_available_timer(monitor_iter, &monitor_iter->available_timer_id);
+ manager_event(EVENT_FLAG_CC, "CCMonitorFailed",
+ "CoreID: %d\r\n"
+ "Callee: %s\r\n",
+ monitor_iter->core_id, monitor_iter->interface->device_name);
+ cc_unref(monitor_iter, "Monitor reported failure. Unref list's reference.");
+ }
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+
+ if (!has_device_monitors(core_instance)) {
+ ast_cc_failed(core_instance->core_id, "All monitors have failed\n");
+ }
+ AST_LIST_UNLOCK(core_instance->monitors);
+ cc_unref(core_instance, "Finished with core_instance in cc_monitor_failed\n");
+
+ ast_free((char *) failure_data->device_name);
+ ast_free((char *) failure_data->debug);
+ ast_free(failure_data);
+ return 0;
+}
+
+int ast_cc_monitor_failed(int core_id, const char *const monitor_name, const char * const debug, ...)
+{
+ struct ast_cc_monitor_failure_data *failure_data;
+ int res;
+ va_list ap;
+
+ if (!(failure_data = ast_calloc(1, sizeof(*failure_data)))) {
+ return -1;
+ }
+
+ if (!(failure_data->device_name = ast_strdup(monitor_name))) {
+ ast_free(failure_data);
+ return -1;
+ }
+
+ va_start(ap, debug);
+ if (ast_vasprintf(&failure_data->debug, debug, ap) == -1) {
+ va_end(ap);
+ ast_free((char *)failure_data->device_name);
+ ast_free(failure_data);
+ return -1;
+ }
+ va_end(ap);
+
+ failure_data->core_id = core_id;
+
+ res = ast_taskprocessor_push(cc_core_taskprocessor, cc_monitor_failed, failure_data);
+ if (res) {
+ ast_free((char *)failure_data->device_name);
+ ast_free((char *)failure_data->debug);
+ ast_free(failure_data);
+ }
+ return res;
+}
+
+static int cc_status_request(void *data)
+{
+ struct cc_core_instance *core_instance= data;
+ int res;
+
+ res = core_instance->agent->callbacks->status_request(core_instance->agent);
+ cc_unref(core_instance, "Status request finished. Unref core instance");
+ return res;
+}
+
+int ast_cc_monitor_status_request(int core_id)
+{
+ int res;
+ struct cc_core_instance *core_instance = find_cc_core_instance(core_id);
+
+ if (!core_instance) {
+ return -1;
+ }
+
+ res = ast_taskprocessor_push(cc_core_taskprocessor, cc_status_request, core_instance);
+ if (res) {
+ cc_unref(core_instance, "Unref core instance. ast_taskprocessor_push failed");
+ }
+ return res;
+}
+
+static int cc_stop_ringing(void *data)
+{
+ struct cc_core_instance *core_instance = data;
+ int res = 0;
+
+ if (core_instance->agent->callbacks->stop_ringing) {
+ res = core_instance->agent->callbacks->stop_ringing(core_instance->agent);
+ }
+ /* If an agent is being asked to stop ringing, then he needs to be prepared if for
+ * whatever reason he needs to be called back again. The proper state to be in to
+ * detect such a circumstance is the CC_ACTIVE state.
+ *
+ * We get to this state using the slightly unintuitive method of calling
+ * ast_cc_monitor_request_acked because it gets us to the proper state.
+ */
+ ast_cc_monitor_request_acked(core_instance->core_id, "Agent %s asked to stop ringing. Be prepared to be recalled again.",
+ core_instance->agent->device_name);
+ cc_unref(core_instance, "Stop ringing finished. Unref core_instance");
+ return res;
+}
+
+int ast_cc_monitor_stop_ringing(int core_id)
+{
+ int res;
+ struct cc_core_instance *core_instance = find_cc_core_instance(core_id);
+
+ if (!core_instance) {
+ return -1;
+ }
+
+ res = ast_taskprocessor_push(cc_core_taskprocessor, cc_stop_ringing, core_instance);
+ if (res) {
+ cc_unref(core_instance, "Unref core instance. ast_taskprocessor_push failed");
+ }
+ return res;
+}
+
+static int cc_party_b_free(void *data)
+{
+ struct cc_core_instance *core_instance = data;
+ int res = 0;
+
+ if (core_instance->agent->callbacks->party_b_free) {
+ res = core_instance->agent->callbacks->party_b_free(core_instance->agent);
+ }
+ cc_unref(core_instance, "Party B free finished. Unref core_instance");
+ return res;
+}
+
+int ast_cc_monitor_party_b_free(int core_id)
+{
+ int res;
+ struct cc_core_instance *core_instance = find_cc_core_instance(core_id);
+
+ if (!core_instance) {
+ return -1;
+ }
+
+ res = ast_taskprocessor_push(cc_core_taskprocessor, cc_party_b_free, core_instance);
+ if (res) {
+ cc_unref(core_instance, "Unref core instance. ast_taskprocessor_push failed");
+ }
+ return res;
+}
+
+struct cc_status_response_args {
+ struct cc_core_instance *core_instance;
+ enum ast_device_state devstate;
+};
+
+static int cc_status_response(void *data)
+{
+ struct cc_status_response_args *args = data;
+ struct cc_core_instance *core_instance = args->core_instance;
+ struct ast_cc_monitor *monitor_iter;
+ enum ast_device_state devstate = args->devstate;
+
+ ast_free(args);
+
+ AST_LIST_LOCK(core_instance->monitors);
+ AST_LIST_TRAVERSE(core_instance->monitors, monitor_iter, next) {
+ if (monitor_iter->interface->monitor_class == AST_CC_DEVICE_MONITOR &&
+ monitor_iter->callbacks->status_response) {
+ monitor_iter->callbacks->status_response(monitor_iter, devstate);
+ }
+ }
+ AST_LIST_UNLOCK(core_instance->monitors);
+ cc_unref(core_instance, "Status response finished. Unref core instance");
+ return 0;
+}
+
+int ast_cc_agent_status_response(int core_id, enum ast_device_state devstate)
+{
+ struct cc_status_response_args *args;
+ struct cc_core_instance *core_instance;
+ int res;
+
+ args = ast_calloc(1, sizeof(*args));
+ if (!args) {
+ return -1;
+ }
+
+ core_instance = find_cc_core_instance(core_id);
+ if (!core_instance) {
+ ast_free(args);
+ return -1;
+ }
+
+ args->core_instance = core_instance;
+ args->devstate = devstate;
+
+ res = ast_taskprocessor_push(cc_core_taskprocessor, cc_status_response, args);
+ if (res) {
+ cc_unref(core_instance, "Unref core instance. ast_taskprocessor_push failed");
+ ast_free(args);
+ }
+ return res;
+}
+
+static int cc_build_payload(struct ast_channel *chan, struct ast_cc_config_params *cc_params,
+ const char *monitor_type, const char * const device_name, const char * dialstring,
+ enum ast_cc_service_type service, void *private_data, struct cc_control_payload *payload)
+{
+ struct ast_datastore *datastore;
+ struct dialed_cc_interfaces *cc_interfaces;
+ int dial_parent_id;
+
+ ast_channel_lock(chan);
+ datastore = ast_channel_datastore_find(chan, &dialed_cc_interfaces_info, NULL);
+ if (!datastore) {
+ ast_channel_unlock(chan);
+ return -1;
+ }
+ cc_interfaces = datastore->data;
+ dial_parent_id = cc_interfaces->dial_parent_id;
+ ast_channel_unlock(chan);
+
+ payload->monitor_type = monitor_type;
+ payload->private_data = private_data;
+ payload->service = service;
+ ast_cc_copy_config_params(&payload->config_params, cc_params);
+ payload->parent_interface_id = dial_parent_id;
+ ast_copy_string(payload->device_name, device_name, sizeof(payload->device_name));
+ ast_copy_string(payload->dialstring, dialstring, sizeof(payload->dialstring));
+ return 0;
+}
+
+int ast_queue_cc_frame(struct ast_channel *chan, const char *monitor_type,
+ const char * const dialstring, enum ast_cc_service_type service, void *private_data)
+{
+ struct ast_frame frame = {0,};
+ char device_name[AST_CHANNEL_NAME];
+ int retval;
+ struct ast_cc_config_params *cc_params;
+
+ cc_params = ast_channel_get_cc_config_params(chan);
+ if (!cc_params) {
+ return -1;
+ }
+ ast_channel_get_device_name(chan, device_name, sizeof(device_name));
+ if (ast_cc_monitor_count(device_name, monitor_type) >= ast_get_cc_max_monitors(cc_params)) {
+ ast_log(LOG_NOTICE, "Not queuing a CC frame for device %s since it already has its maximum monitors allocated\n", device_name);
+ return -1;
+ }
+
+ if (ast_cc_build_frame(chan, cc_params, monitor_type, device_name, dialstring, service, private_data, &frame)) {
+ /* Frame building failed. We can't use this. */
+ return -1;
+ }
+ retval = ast_queue_frame(chan, &frame);
+ ast_frfree(&frame);
+ return retval;
+}
+
+int ast_cc_build_frame(struct ast_channel *chan, struct ast_cc_config_params *cc_params,
+ const char *monitor_type, const char * const device_name,
+ const char * const dialstring, enum ast_cc_service_type service, void *private_data,
+ struct ast_frame *frame)
+{
+ struct cc_control_payload *payload = ast_calloc(1, sizeof(*payload));
+
+ if (!payload) {
+ return -1;
+ }
+ if (cc_build_payload(chan, cc_params, monitor_type, device_name, dialstring, service, private_data, payload)) {
+ /* Something screwed up, we can't make a frame with this */
+ ast_free(payload);
+ return -1;
+ }
+ frame->frametype = AST_FRAME_CONTROL;
+ frame->subclass.integer = AST_CONTROL_CC;
+ frame->data.ptr = payload;
+ frame->datalen = sizeof(*payload);
+ frame->mallocd = AST_MALLOCD_DATA;
+ return 0;
+}
+
+void ast_cc_call_failed(struct ast_channel *incoming, struct ast_channel *outgoing, const char * const dialstring)
+{
+ char device_name[AST_CHANNEL_NAME];
+ struct cc_control_payload payload;
+ struct ast_cc_config_params *cc_params;
+
+ if (outgoing->hangupcause != AST_CAUSE_BUSY && outgoing->hangupcause != AST_CAUSE_CONGESTION) {
+ /* It doesn't make sense to try to offer CCBS to the caller if the reason for ast_call
+ * failing is something other than busy or congestion
+ */
+ return;
+ }
+
+ cc_params = ast_channel_get_cc_config_params(outgoing);
+ if (!cc_params) {
+ return;
+ }
+ if (ast_get_cc_monitor_policy(cc_params) != AST_CC_MONITOR_GENERIC) {
+ /* This sort of CCBS only works if using generic CC. For native, we would end up sending
+ * a CC request for a non-existent call. The far end will reject this every time
+ */
+ return;
+ }
+
+ ast_channel_get_device_name(outgoing, device_name, sizeof(device_name));
+ if (cc_build_payload(outgoing, cc_params, AST_CC_GENERIC_MONITOR_TYPE, device_name,
+ dialstring, AST_CC_CCBS, NULL, &payload)) {
+ /* Something screwed up, we can't make a frame with this */
+ return;
+ }
+ ast_handle_cc_control_frame(incoming, outgoing, &payload);
+}
+
+void ast_cc_busy_interface(struct ast_channel *inbound, struct ast_cc_config_params *cc_params,
+ const char *monitor_type, const char * const device_name, const char * const dialstring, void *private_data)
+{
+ struct cc_control_payload payload;
+ if (cc_build_payload(inbound, cc_params, monitor_type, device_name, dialstring, AST_CC_CCBS, private_data, &payload)) {
+ /* Something screwed up. Don't try to handle this payload */
+ call_destructor_with_no_monitor(monitor_type, private_data);
+ return;
+ }
+ ast_handle_cc_control_frame(inbound, NULL, &payload);
+}
+
+int ast_cc_callback(struct ast_channel *inbound, const char * const tech, const char * const dest, ast_cc_callback_fn callback)
+{
+ const struct ast_channel_tech *chantech = ast_get_channel_tech(tech);
+
+ if (chantech && chantech->cc_callback) {
+ chantech->cc_callback(inbound, dest, callback);
+ }
+
+ return 0;
+}
+
+static const char *ccreq_app = "CallCompletionRequest";
+
+static int ccreq_exec(struct ast_channel *chan, const char *data)
+{
+ struct cc_core_instance *core_instance;
+ char device_name[AST_CHANNEL_NAME];
+ unsigned long match_flags;
+ int res;
+
+ ast_channel_get_device_name(chan, device_name, sizeof(device_name));
+
+ match_flags = MATCH_NO_REQUEST;
+ if (!(core_instance = ao2_t_callback_data(cc_core_instances, 0, match_agent, device_name, &match_flags, "Find core instance for CallCompletionRequest"))) {
+ ast_log_dynamic_level(cc_logger_level, "Couldn't find a core instance for caller %s\n", device_name);
+ return -1;
+ }
+
+ ast_log_dynamic_level(cc_logger_level, "Core %d: Found core_instance for caller %s\n",
+ core_instance->core_id, device_name);
+
+ if (strcmp(core_instance->agent->callbacks->type, "generic")) {
+ ast_log_dynamic_level(cc_logger_level, "Core %d: CallCompletionRequest is only for generic agent types.\n",
+ core_instance->core_id);
+ pbx_builtin_setvar_helper(chan, "CC_REQUEST_RESULT", "FAIL");
+ cc_unref(core_instance, "Unref core_instance since CallCompletionRequest was called with native agent");
+ return 0;
+ }
+
+ if (!ast_cc_request_is_within_limits()) {
+ ast_log_dynamic_level(cc_logger_level, "Core %d: CallCompletionRequest failed. Too many requests in the system\n",
+ core_instance->core_id);
+ ast_cc_failed(core_instance->core_id, "Too many CC requests\n");
+ pbx_builtin_setvar_helper(chan, "CC_REQUEST_RESULT", "FAIL");
+ cc_unref(core_instance, "Unref core_instance since too many CC requests");
+ return 0;
+ }
+
+ res = ast_cc_agent_accept_request(core_instance->core_id, "CallCompletionRequest called by caller %s for core_id %d", device_name, core_instance->core_id);
+ pbx_builtin_setvar_helper(chan, "CC_REQUEST_RESULT", res ? "FAIL" : "SUCCESS");
+ cc_unref(core_instance, "Done with CallCompletionRequest");
+ return res;
+}
+
+static const char *cccancel_app = "CallCompletionCancel";
+
+static int cccancel_exec(struct ast_channel *chan, const char *data)
+{
+ struct cc_core_instance *core_instance;
+ char device_name[AST_CHANNEL_NAME];
+ unsigned long match_flags;
+ int res;
+
+ ast_channel_get_device_name(chan, device_name, sizeof(device_name));
+
+ match_flags = MATCH_REQUEST;
+ if (!(core_instance = ao2_t_callback_data(cc_core_instances, 0, match_agent, device_name, &match_flags, "Find core instance for CallCompletionCancel"))) {
+ ast_log(LOG_WARNING, "Cannot find CC transaction to cancel for caller %s\n", device_name);
+ return -1;
+ }
+
+ if (strcmp(core_instance->agent->callbacks->type, "generic")) {
+ ast_log(LOG_WARNING, "CallCompletionCancel may only be used for calles with a generic agent\n");
+ cc_unref(core_instance, "Unref core instance found during CallCompletionCancel");
+ return -1;
+ }
+ res = ast_cc_failed(core_instance->core_id, "Call completion request Cancelled for core ID %d by caller %s",
+ core_instance->core_id, device_name);
+ cc_unref(core_instance, "Unref core instance found during CallCompletionCancel");
+ return res;
+}
+
+struct count_monitors_cb_data {
+ const char *device_name;
+ const char *monitor_type;
+ int count;
+};
+
+static int count_monitors_cb(void *obj, void *arg, int flags)
+{
+ struct cc_core_instance *core_instance = obj;
+ struct count_monitors_cb_data *cb_data = arg;
+ const char *device_name = cb_data->device_name;
+ const char *monitor_type = cb_data->monitor_type;
+ struct ast_cc_monitor *monitor_iter;
+
+ AST_LIST_LOCK(core_instance->monitors);
+ AST_LIST_TRAVERSE(core_instance->monitors, monitor_iter, next) {
+ if (!strcmp(monitor_iter->interface->device_name, device_name) &&
+ !strcmp(monitor_iter->interface->monitor_type, monitor_type)) {
+ cb_data->count++;
+ break;
+ }
+ }
+ AST_LIST_UNLOCK(core_instance->monitors);
+ return 0;
+}
+
+int ast_cc_monitor_count(const char * const name, const char * const type)
+{
+ struct count_monitors_cb_data data = {.device_name = name, .monitor_type = type,};
+
+ ao2_t_callback(cc_core_instances, OBJ_NODATA, count_monitors_cb, &data, "Counting agents");
+ ast_log_dynamic_level(cc_logger_level, "Counted %d monitors\n", data.count);
+ return data.count;
+}
+
+static void initialize_cc_max_requests(void)
+{
+ struct ast_config *cc_config;
+ const char *cc_max_requests_str;
+ struct ast_flags config_flags = {0,};
+ char *endptr;
+
+ cc_config = ast_config_load2("ccss.conf", "ccss", config_flags);
+ if (!cc_config || cc_config == CONFIG_STATUS_FILEINVALID) {
+ ast_log(LOG_WARNING, "Could not find valid ccss.conf file. Using cc_max_requests default\n");
+ global_cc_max_requests = GLOBAL_CC_MAX_REQUESTS_DEFAULT;
+ return;
+ }
+
+ if (!(cc_max_requests_str = ast_variable_retrieve(cc_config, "general", "cc_max_requests"))) {
+ ast_config_destroy(cc_config);
+ ast_log(LOG_WARNING, "No cc_max_requests defined. Using default\n");
+ global_cc_max_requests = GLOBAL_CC_MAX_REQUESTS_DEFAULT;
+ return;
+ }
+
+ global_cc_max_requests = strtol(cc_max_requests_str, &endptr, 10);
+
+ if (!ast_strlen_zero(endptr)) {
+ ast_log(LOG_WARNING, "Invalid input given for cc_max_requests. Using default\n");
+ global_cc_max_requests = GLOBAL_CC_MAX_REQUESTS_DEFAULT;
+ }
+
+ ast_config_destroy(cc_config);
+ return;
+}
+
+static void cc_cli_print_monitor_stats(struct ast_cc_monitor *monitor, int fd, int parent_id)
+{
+ struct ast_cc_monitor *child_monitor_iter = monitor;
+ if (!monitor) {
+ return;
+ }
+
+ ast_cli(fd, "\t\t|-->%s", monitor->interface->device_name);
+ if (monitor->interface->monitor_class == AST_CC_DEVICE_MONITOR) {
+ ast_cli(fd, "(%s)", cc_service_to_string(monitor->service_offered));
+ }
+ ast_cli(fd, "\n");
+
+ while ((child_monitor_iter = AST_LIST_NEXT(child_monitor_iter, next))) {
+ if (child_monitor_iter->parent_id == monitor->id) {
+ cc_cli_print_monitor_stats(child_monitor_iter, fd, child_monitor_iter->id);
+ }
+ }
+}
+
+static int print_stats_cb(void *obj, void *arg, int flags)
+{
+ int *cli_fd = arg;
+ struct cc_core_instance *core_instance = obj;
+
+ ast_cli(*cli_fd, "%d\t\t%s\t\t%s\n", core_instance->core_id, core_instance->agent->device_name,
+ cc_state_to_string(core_instance->current_state));
+ AST_LIST_LOCK(core_instance->monitors);
+ cc_cli_print_monitor_stats(AST_LIST_FIRST(core_instance->monitors), *cli_fd, 0);
+ AST_LIST_UNLOCK(core_instance->monitors);
+ return 0;
+}
+
+static int cc_cli_output_status(void *data)
+{
+ int *cli_fd = data;
+ int count = ao2_container_count(cc_core_instances);
+
+ if (!count) {
+ ast_cli(*cli_fd, "There are currently no active call completion transactions\n");
+ } else {
+ ast_cli(*cli_fd, "%d Call completion transactions\n", count);
+ ast_cli(*cli_fd, "Core ID\t\tCaller\t\t\t\tStatus\n");
+ ast_cli(*cli_fd, "----------------------------------------------------------------------------\n");
+ ao2_t_callback(cc_core_instances, OBJ_NODATA, print_stats_cb, cli_fd, "Printing stats to CLI");
+ }
+ ast_free(cli_fd);
+ return 0;
+}
+
+static char *handle_cc_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ int *cli_fd;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "cc report status";
+ e->usage =
+ "Usage: cc report status\n"
+ " Report the current status of any ongoing CC transactions\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3) {
+ return CLI_SHOWUSAGE;
+ }
+
+ cli_fd = ast_malloc(sizeof(*cli_fd));
+ if (!cli_fd) {
+ return CLI_FAILURE;
+ }
+
+ *cli_fd = a->fd;
+
+ if (ast_taskprocessor_push(cc_core_taskprocessor, cc_cli_output_status, cli_fd)) {
+ ast_free(cli_fd);
+ return CLI_FAILURE;
+ }
+ return CLI_SUCCESS;
+}
+
+static int kill_cores(void *obj, void *arg, int flags)
+{
+ int *core_id = arg;
+ struct cc_core_instance *core_instance = obj;
+
+ if (!core_id || (core_instance->core_id == *core_id)) {
+ ast_cc_failed(core_instance->core_id, "CC transaction canceled administratively\n");
+ }
+ return 0;
+}
+
+static char *complete_core_id(const char *line, const char *word, int pos, int state)
+{
+ int which = 0;
+ int wordlen = strlen(word);
+ char *ret = NULL;
+ struct ao2_iterator core_iter = ao2_iterator_init(cc_core_instances, 0);
+ struct cc_core_instance *core_instance;
+
+ for (; (core_instance = ao2_t_iterator_next(&core_iter, "Next core instance"));
+ cc_unref(core_instance, "CLI tab completion iteration")) {
+ char core_id_str[20];
+ snprintf(core_id_str, sizeof(core_id_str), "%d", core_instance->core_id);
+ if (!strncmp(word, core_id_str, wordlen) && ++which > state) {
+ ret = ast_strdup(core_id_str);
+ cc_unref(core_instance, "Found a matching core ID for CLI tab-completion");
+ break;
+ }
+ }
+ ao2_iterator_destroy(&core_iter);
+
+ return ret;
+}
+
+static char *handle_cc_kill(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ static const char * const option[] = { "core", "all", NULL };
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "cc cancel";
+ e->usage =
+ "Usage: cc cancel can be used in two ways.\n"
+ " 1. 'cc cancel core [core ID]' will cancel the CC transaction with\n"
+ " core ID equal to the specified core ID.\n"
+ " 2. 'cc cancel all' will cancel all active CC transactions.\n";
+ return NULL;
+ case CLI_GENERATE:
+ if (a->pos == 2) {
+ return ast_cli_complete(a->word, option, a->n);
+ }
+ if (a->pos == 3) {
+ return complete_core_id(a->line, a->word, a->pos, a->n);
+ }
+ return NULL;
+ }
+
+ if (a->argc == 4) {
+ int core_id;
+ char *endptr;
+ if (strcasecmp(a->argv[2], "core")) {
+ return CLI_SHOWUSAGE;
+ }
+ core_id = strtol(a->argv[3], &endptr, 10);
+ if ((errno != 0 && core_id == 0) || (endptr == a->argv[3])) {
+ return CLI_SHOWUSAGE;
+ }
+ ao2_t_callback(cc_core_instances, OBJ_NODATA, kill_cores, &core_id, "CLI Killing Core Id");
+ } else if (a->argc == 3) {
+ if (strcasecmp(a->argv[2], "all")) {
+ return CLI_SHOWUSAGE;
+ }
+ ao2_t_callback(cc_core_instances, OBJ_NODATA, kill_cores, NULL, "CLI Killing all CC cores");
+ } else {
+ return CLI_SHOWUSAGE;
+ }
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cc_cli[] = {
+ AST_CLI_DEFINE(handle_cc_status, "Reports CC stats"),
+ AST_CLI_DEFINE(handle_cc_kill, "Kill a CC transaction"),
+};
+
+int ast_cc_init(void)
+{
+ int res;
+
+ if (!(cc_core_instances = ao2_t_container_alloc(CC_CORE_INSTANCES_BUCKETS,
+ cc_core_instance_hash_fn, cc_core_instance_cmp_fn,
+ "Create core instance container"))) {
+ return -1;
+ }
+ if (!(generic_monitors = ao2_t_container_alloc(CC_CORE_INSTANCES_BUCKETS,
+ generic_monitor_hash_fn, generic_monitor_cmp_fn,
+ "Create generic monitor container"))) {
+ return -1;
+ }
+ if (!(cc_core_taskprocessor = ast_taskprocessor_get("CCSS core", TPS_REF_DEFAULT))) {
+ return -1;
+ }
+ if (!(cc_sched_thread = ast_sched_thread_create())) {
+ return -1;
+ }
+ res = ast_register_application2(ccreq_app, ccreq_exec, NULL, NULL, NULL);
+ res |= ast_register_application2(cccancel_app, cccancel_exec, NULL, NULL, NULL);
+ res |= ast_cc_monitor_register(&generic_monitor_cbs);
+ res |= ast_cc_agent_register(&generic_agent_callbacks);
+ ast_cli_register_multiple(cc_cli, ARRAY_LEN(cc_cli));
+ cc_logger_level = ast_logger_register_level(CC_LOGGER_LEVEL_NAME);
+ dialed_cc_interface_counter = 1;
+ initialize_cc_max_requests();
+ return res;
+}