From 0eb1e5407a6eacd46d98e134dc81e8b857c103b7 Mon Sep 17 00:00:00 2001 From: mmichelson Date: Fri, 9 Apr 2010 15:31:32 +0000 Subject: Merge Call completion support into trunk. From Reviewboard: CCSS stands for Call Completion Supplementary Services. An admittedly out-of-date overview of the architecture can be found in the file doc/CCSS_architecture.pdf in the CCSS branch. Off the top of my head, the big differences between what is implemented and what is in the document are as follows: 1. We did not end up modifying the Hangup application at all. 2. The document states that a single call completion monitor may be used across multiple calls to the same device. This proved to not be such a good idea when implementing protocol-specific monitors, and so we ended up using one monitor per-device per-call. 3. There are some configuration options which were conceived after the document was written. These are documented in the ccss.conf.sample that is on this review request. For some basic understanding of terminology used throughout this code, see the ccss.tex document that is on this review. This implements CCBS and CCNR in several flavors. First up is a "generic" implementation, which can work over any channel technology provided that the channel technology can accurately report device state. Call completion is requested using the dialplan application CallCompletionRequest and can be canceled using CallCompletionCancel. Device state subscriptions are used in order to monitor the state of called parties. Next, there is a SIP-specific implementation of call completion. This method uses the methods outlined in draft-ietf-bliss-call-completion-06 to implement call completion using SIP signaling. There are a few things to note here: * The agent/monitor terminology used throughout Asterisk sometimes is the reverse of what is defined in the referenced draft. * Implementation of the draft required support for SIP PUBLISH. I attempted to write this in a generic-enough fashion such that if someone were to want to write PUBLISH support for other event packages, such as dialog-state or presence, most of the effort would be in writing callbacks specific to the event package. * A subportion of supporting PUBLISH reception was that we had to implement a PIDF parser. The PIDF support added is a bit minimal. I first wrote a validation routine to ensure that the PIDF document is formatted properly. The rest of the PIDF reading is done in-line in the call-completion-specific PUBLISH-handling code. In other words, while there is PIDF support here, it is not in any state where it could easily be applied to other event packages as is. Finally, there are a variety of ISDN-related call completion protocols supported. These were written by Richard Mudgett, and as such I can't really say much about their implementation. There are notes in the CHANGES file that indicate the ISDN protocols over which call completion is supported. Review: https://reviewboard.asterisk.org/r/523 git-svn-id: http://svn.digium.com/svn/asterisk/trunk@256528 f38db490-d61c-443f-a65b-d21fe96a405b --- CHANGES | 7 + apps/app_dial.c | 107 +- channels/chan_dahdi.c | 915 +++- channels/chan_local.c | 16 + channels/chan_sip.c | 8806 +++++++++++++++++++++++---------------- channels/sig_analog.c | 26 + channels/sig_analog.h | 2 + channels/sig_pri.c | 2092 ++++++++-- channels/sig_pri.h | 102 + channels/sip/include/sip.h | 365 +- configs/ccss.conf.sample | 150 + configs/chan_dahdi.conf.sample | 56 +- configs/manager.conf.sample | 3 + configure.ac | 2 + doc/tex/asterisk.tex | 3 + doc/tex/ccss.tex | 414 ++ funcs/func_callcompletion.c | 114 + include/asterisk/ccss.h | 1582 +++++++ include/asterisk/channel.h | 138 +- include/asterisk/channelstate.h | 53 + include/asterisk/devicestate.h | 2 +- include/asterisk/frame.h | 9 +- include/asterisk/manager.h | 1 + include/asterisk/rtp_engine.h | 1 + include/asterisk/xml.h | 10 + main/asterisk.c | 6 + main/ccss.c | 4157 ++++++++++++++++++ main/channel.c | 105 + main/manager.c | 1 + main/xml.c | 30 + 30 files changed, 15327 insertions(+), 3948 deletions(-) create mode 100644 configs/ccss.conf.sample create mode 100644 doc/tex/ccss.tex create mode 100644 funcs/func_callcompletion.c create mode 100644 include/asterisk/ccss.h create mode 100644 include/asterisk/channelstate.h create mode 100644 main/ccss.c diff --git a/CHANGES b/CHANGES index 2ea3e5ff7..218da9cfa 100644 --- a/CHANGES +++ b/CHANGES @@ -387,6 +387,13 @@ Calendaring for Asterisk iCalendar, CalDAV, and Exchange Server calendars are supported (Exchange support only tested on Exchange Server 2003 with no support for forms-based authentication). +Call Completion Supplementary Services for Asterisk +--------------------------------------------------- + * Call completion support has been added for SIP, DAHDI/ISDN, and DAHDI/analog. + DAHDI/ISDN supports call completion for the following switch types: + EuroIsdn(ETSI) for PTP and PTMP modes, and Qsig. + See doc/CCSS_architecture.pdf and doc/tex/ccss.tex(asterisk.pdf) for details. + Multicast RTP Support --------------------- * A new RTP engine and channel driver have been added which supports Multicast RTP. diff --git a/apps/app_dial.c b/apps/app_dial.c index 8a58932a8..b1de21d5f 100644 --- a/apps/app_dial.c +++ b/apps/app_dial.c @@ -62,6 +62,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/global_datastores.h" #include "asterisk/dsp.h" #include "asterisk/cel.h" +#include "asterisk/ccss.h" #include "asterisk/indications.h" /*** DOCUMENTATION @@ -810,6 +811,12 @@ static void do_forward(struct chanlist *o, ast_channel_make_compatible(o->chan, in); ast_channel_inherit_variables(in, o->chan); ast_channel_datastore_inherit(in, o->chan); + /* When a call is forwarded, we don't want to track new interfaces + * dialed for CC purposes. Setting the done flag will ensure that + * any Dial operations that happen later won't record CC interfaces. + */ + ast_ignore_cc(o->chan); + ast_log(LOG_NOTICE, "Not accepting call completion offers from call-forward recipient %s\n", o->chan->name); } else ast_log(LOG_NOTICE, "Unable to create local channel for call forward to '%s/%s' (cause = %d)\n", tech, stuff, cause); } @@ -904,7 +911,8 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, struct chanlist *outgoing, int *to, struct ast_flags64 *peerflags, char *opt_args[], struct privacy_args *pa, - const struct cause_args *num_in, int *result, char *dtmf_progress) + const struct cause_args *num_in, int *result, char *dtmf_progress, + const int ignore_cc) { struct cause_args num = *num_in; int prestart = num.busy + num.congestion + num.nochan; @@ -917,6 +925,10 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, #endif struct ast_party_connected_line connected_caller; struct ast_str *featurecode = ast_str_alloca(FEATURE_MAX_LEN + 1); + int cc_recall_core_id; + int is_cc_recall; + int cc_frame_received = 0; + int num_ringing = 0; ast_party_connected_line_init(&connected_caller); if (single) { @@ -938,6 +950,8 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, } } + is_cc_recall = ast_cc_is_recall(in, &cc_recall_core_id, NULL); + #ifdef HAVE_EPOLL for (epollo = outgoing; epollo; epollo = epollo->next) ast_poll_channel_add(in, epollo->chan); @@ -970,6 +984,9 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, ast_verb(3, "No one is available to answer at this time (%d:%d/%d/%d)\n", numlines, num.busy, num.congestion, num.nochan); } *to = 0; + if (is_cc_recall) { + ast_cc_failed(cc_recall_core_id, "Everyone is busy/congested for the recall. How sad"); + } return NULL; } winner = ast_waitfor_n(watchers, pos, to); @@ -1014,6 +1031,15 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, /* here, o->chan == c == winner */ if (!ast_strlen_zero(c->call_forward)) { pa->sentringing = 0; + if (!ignore_cc && (f = ast_read(c))) { + if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_CC) { + /* This channel is forwarding the call, and is capable of CC, so + * be sure to add the new device interface to the list + */ + ast_handle_cc_control_frame(in, c, f->data.ptr); + } + ast_frfree(f); + } do_forward(o, &num, peerflags, single, to); continue; } @@ -1088,13 +1114,41 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, handle_cause(AST_CAUSE_CONGESTION, &num); break; case AST_CONTROL_RINGING: - ast_verb(3, "%s is ringing\n", c->name); - /* Setup early media if appropriate */ - if (single && CAN_EARLY_BRIDGE(peerflags, in, c)) - ast_channel_early_bridge(in, c); - if (!(pa->sentringing) && !ast_test_flag64(outgoing, OPT_MUSICBACK) && ast_strlen_zero(opt_args[OPT_ARG_RINGBACK])) { - ast_indicate(in, AST_CONTROL_RINGING); - pa->sentringing++; + /* This is a tricky area to get right when using a native + * CC agent. The reason is that we do the best we can to send only a + * single ringing notification to the caller. + * + * Call completion complicates the logic used here. CCNR is typically + * offered during a ringing message. Let's say that party A calls + * parties B, C, and D. B and C do not support CC requests, but D + * does. If we were to receive a ringing notification from B before + * the others, then we would end up sending a ringing message to + * A with no CCNR offer present. + * + * The approach that we have taken is that if we receive a ringing + * response from a party and no CCNR offer is present, we need to + * wait. Specifically, we need to wait until either a) a called party + * offers CCNR in its ringing response or b) all called parties have + * responded in some way to our call and none offers CCNR. + * + * The drawback to this is that if one of the parties has a delayed + * response or, god forbid, one just plain doesn't respond to our + * outgoing call, then this will result in a significant delay between + * when the caller places the call and hears ringback. + * + * Note also that if CC is disabled for this call, then it is perfectly + * fine for ringing frames to get sent through. + */ + ++num_ringing; + if (ignore_cc || cc_frame_received || num_ringing == numlines) { + ast_verb(3, "%s is ringing\n", c->name); + /* Setup early media if appropriate */ + if (single && CAN_EARLY_BRIDGE(peerflags, in, c)) + ast_channel_early_bridge(in, c); + if (!(pa->sentringing) && !ast_test_flag64(outgoing, OPT_MUSICBACK) && ast_strlen_zero(opt_args[OPT_ARG_RINGBACK])) { + ast_indicate(in, AST_CONTROL_RINGING); + pa->sentringing++; + } } break; case AST_CONTROL_PROGRESS: @@ -1163,6 +1217,12 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, case AST_CONTROL_FLASH: /* Ignore going off hook and flash */ break; + case AST_CONTROL_CC: + if (!ignore_cc) { + ast_handle_cc_control_frame(in, c, f->data.ptr); + cc_frame_received = 1; + } + break; case -1: if (!ast_test_flag64(outgoing, OPT_RINGBACK | OPT_MUSICBACK)) { ast_verb(3, "%s stopped sounds\n", c->name); @@ -1212,6 +1272,9 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, } ast_frfree(f); } + if (is_cc_recall) { + ast_cc_completed(in, "CC completed, although the caller hung up (cancelled)"); + } return NULL; } @@ -1229,6 +1292,9 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, strcpy(pa->status, "CANCEL"); ast_frfree(f); ast_channel_unlock(in); + if (is_cc_recall) { + ast_cc_completed(in, "CC completed, but the caller used DTMF to exit"); + } return NULL; } ast_channel_unlock(in); @@ -1241,6 +1307,9 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, strcpy(pa->status, "CANCEL"); ast_cdr_noanswer(in->cdr); ast_frfree(f); + if (is_cc_recall) { + ast_cc_completed(in, "CC completed, but the caller hung up with DTMF"); + } return NULL; } } @@ -1283,6 +1352,9 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in, } #endif + if (is_cc_recall) { + ast_cc_completed(in, "Recall completed!"); + } return peer; } @@ -1656,6 +1728,8 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast char *opt_args[OPT_ARG_ARRAY_SIZE]; struct ast_datastore *datastore = NULL; int fulldial = 0, num_dialed = 0; + int ignore_cc = 0; + char device_name[AST_CHANNEL_NAME]; /* Reset all DIAL variables back to blank, to prevent confusion (in case we don't reset all of them). */ pbx_builtin_setvar_helper(chan, "DIALSTATUS", ""); @@ -1686,6 +1760,10 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast goto done; } + if (ast_cc_call_init(chan, &ignore_cc)) { + goto done; + } + if (ast_test_flag64(&opts, OPT_SCREEN_NOINTRO) && !ast_strlen_zero(opt_args[OPT_ARG_SCREEN_NOINTRO])) { delprivintro = atoi(opt_args[OPT_ARG_SCREEN_NOINTRO]); @@ -1871,8 +1949,17 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast if (!rest) /* we are on the last destination */ chan->hangupcause = cause; chanlist_free(tmp); + if (!ignore_cc && (cause == AST_CAUSE_BUSY || cause == AST_CAUSE_CONGESTION)) { + if (!ast_cc_callback(chan, tech, numsubst, ast_cc_busy_interface)) { + ast_cc_extension_monitor_add_dialstring(chan, interface, ""); + } + } continue; } + ast_channel_get_device_name(tc, device_name, sizeof(device_name)); + if (!ignore_cc) { + ast_cc_extension_monitor_add_dialstring(chan, interface, device_name); + } pbx_builtin_setvar_helper(tc, "DIALEDPEERNUMBER", numsubst); ast_channel_lock(tc); @@ -1965,6 +2052,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast chan->hangupcause = tc->hangupcause; } ast_channel_unlock(chan); + ast_cc_call_failed(chan, tc, interface); ast_hangup(tc); tc = NULL; chanlist_free(tmp); @@ -2038,7 +2126,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast } } - peer = wait_for_answer(chan, outgoing, &to, peerflags, opt_args, &pa, &num, &result, dtmf_progress); + peer = wait_for_answer(chan, outgoing, &to, peerflags, opt_args, &pa, &num, &result, dtmf_progress, ignore_cc); /* The ast_channel_datastore_remove() function could fail here if the * datastore was moved to another channel during a masquerade. If this is @@ -2513,6 +2601,7 @@ done: if (config.start_sound) { ast_free((char *)config.start_sound); } + ast_ignore_cc(chan); return res; } diff --git a/channels/chan_dahdi.c b/channels/chan_dahdi.c index 356106e4a..d9ae8a6f5 100644 --- a/channels/chan_dahdi.c +++ b/channels/chan_dahdi.c @@ -116,6 +116,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/event.h" #include "asterisk/devicestate.h" #include "asterisk/paths.h" +#include "asterisk/ccss.h" /*** DOCUMENTATION @@ -608,6 +609,11 @@ struct dahdi_pri { static struct dahdi_pri pris[NUM_SPANS]; +#if defined(HAVE_PRI_CCSS) +/*! DAHDI PRI CCSS agent and monitor type name. */ +static const char dahdi_pri_cc_type[] = "DAHDI/PRI"; +#endif /* defined(HAVE_PRI_CCSS) */ + #else /*! Shut up the compiler */ struct dahdi_pri; @@ -1252,6 +1258,14 @@ struct dahdi_pvt { /*! \brief TRUE if confrence is muted. */ int muting; void *sig_pvt; + struct ast_cc_config_params *cc_params; + /* DAHDI channel names may differ greatly from the + * string that was provided to an app such as Dial. We + * need to save the original string passed to dahdi_request + * for call completion purposes. This way, we can replicate + * the original dialed string later. + */ + char dialstring[AST_CHANNEL_NAME]; }; static struct dahdi_pvt *iflist = NULL; /*!< Main interface list start */ @@ -1315,6 +1329,12 @@ static struct dahdi_chan_conf dahdi_chan_conf_default(void) .nodetype = PRI_CPE, .qsigchannelmapping = DAHDI_CHAN_MAPPING_PHYSICAL, +#if defined(HAVE_PRI_CCSS) + .cc_ptmp_recall_mode = 1,/* specificRecall */ + .cc_qsig_signaling_link_req = 1,/* retain */ + .cc_qsig_signaling_link_rsp = 1,/* retain */ +#endif /* defined(HAVE_PRI_CCSS) */ + .minunused = 2, .idleext = "", .idledial = "", @@ -1398,6 +1418,7 @@ static struct dahdi_chan_conf dahdi_chan_conf_default(void) .buf_policy = DAHDI_POLICY_IMMEDIATE, .buf_no = numbufs, .usefaxbuffers = 0, + .cc_params = ast_cc_config_params_init(), }, .timing = { .prewinktime = -1, @@ -1433,6 +1454,8 @@ static int dahdi_setoption(struct ast_channel *chan, int option, void *data, int static int dahdi_queryoption(struct ast_channel *chan, int option, void *data, int *datalen); static int dahdi_func_read(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len); static int dahdi_func_write(struct ast_channel *chan, const char *function, char *data, const char *value); +static int dahdi_devicestate(void *data); +static int dahdi_cc_callback(struct ast_channel *inbound, const char *dest, ast_cc_callback_fn callback); static const struct ast_channel_tech dahdi_tech = { .type = "DAHDI", @@ -1455,6 +1478,8 @@ static const struct ast_channel_tech dahdi_tech = { .queryoption = dahdi_queryoption, .func_channel_read = dahdi_func_read, .func_channel_write = dahdi_func_write, + .devicestate = dahdi_devicestate, + .cc_callback = dahdi_cc_callback, }; #define GET_CHANNEL(p) ((p)->channel) @@ -2152,6 +2177,13 @@ static void my_set_pulsedial(void *pvt, int flag) p->pulsedial = flag; } +static const char *my_get_orig_dialstring(void *pvt) +{ + struct dahdi_pvt *p = pvt; + + return p->dialstring; +} + static void my_increase_ss_count(void) { ast_mutex_lock(&ss_thread_lock); @@ -2785,6 +2817,160 @@ static void my_pri_set_rdnis(void *pvt, const char *rdnis) ast_copy_string(p->rdnis, rdnis, sizeof(p->rdnis)); } +/*! + * \internal + * \brief Make a dialstring for native ISDN CC to recall properly. + * \since 1.8 + * + * \param priv Channel private control structure. + * \param buf Where to put the modified dialstring. + * \param buf_size Size of modified dialstring buffer. + * + * \details + * original dialstring: + * DAHDI/[i-][c|r|d][/extension[/options]] + * DAHDI/[i-](g|G|r|R)[c|r|d][/extension[/options]] + * + * The modified dialstring will have prefixed the channel-group section + * with the ISDN channel restriction. + * + * buf: + * DAHDI/i-[c|r|d][/extension[/options]] + * DAHDI/i-(g|G|r|R)[c|r|d][/extension[/options]] + * + * The routine will check to see if the ISDN channel restriction is already + * in the original dialstring. + * + * \return Nothing + */ +static void my_pri_make_cc_dialstring(void *priv, char *buf, size_t buf_size) +{ + char *dial; + struct dahdi_pvt *pvt; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(tech); /* channel technology token */ + AST_APP_ARG(group); /* channel/group token */ + //AST_APP_ARG(ext); /* extension token */ + //AST_APP_ARG(opts); /* options token */ + //AST_APP_ARG(other); /* Any remining unused arguments */ + ); + + pvt = priv; + dial = ast_strdupa(pvt->dialstring); + AST_NONSTANDARD_APP_ARGS(args, dial, '/'); + if (!args.tech) { + ast_copy_string(buf, pvt->dialstring, buf_size); + return; + } + if (!args.group) { + /* Append the ISDN span channel restriction to the dialstring. */ + snprintf(buf, buf_size, "%s/i%d-", args.tech, pvt->pri->span); + return; + } + if (args.group[0] == 'i') { + /* The ISDN span channel restriction is already in the dialstring. */ + ast_copy_string(buf, pvt->dialstring, buf_size); + return; + } + /* Insert the ISDN span channel restriction into the dialstring. */ + snprintf(buf, buf_size, "%s/i%d-%s", args.tech, pvt->pri->span, args.group); +} + +/*! + * \internal + * \brief Reevaluate the PRI span device state. + * \since 1.8 + * + * \param pri Asterisk D channel control structure. + * + * \return Nothing + * + * \note Assumes the pri->lock is already obtained. + */ +static void dahdi_pri_update_span_devstate(struct sig_pri_pri *pri) +{ + unsigned idx; + unsigned num_b_chans; /* Number of B channels provisioned on the span. */ + unsigned in_use; /* Number of B channels in use on the span. */ + unsigned in_alarm; /* TRUE if the span is in alarm condition. */ + enum ast_device_state new_state; + + /* Count the number of B channels and the number of B channels in use. */ + num_b_chans = 0; + in_use = 0; + in_alarm = 1; + for (idx = pri->numchans; idx--;) { + if (pri->pvts[idx] && !pri->pvts[idx]->no_b_channel) { + /* This is a B channel interface. */ + ++num_b_chans; + if (pri->pvts[idx]->owner +#if defined(HAVE_PRI_SERVICE_MESSAGES) + /* Out-of-service B channels are "in-use". */ + && pri->pvts[idx]->service_status +#endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ + ) { + ++in_use; + } + if (!pri->pvts[idx]->inalarm) { + /* There is a channel that is not in alarm. */ + in_alarm = 0; + } + } + } + + /* Update the span congestion device state and report any change. */ + if (in_alarm) { + new_state = AST_DEVICE_UNAVAILABLE; + } else { + new_state = num_b_chans == in_use ? AST_DEVICE_BUSY : AST_DEVICE_NOT_INUSE; + } + if (pri->congestion_devstate != new_state) { + pri->congestion_devstate = new_state; + ast_devstate_changed(AST_DEVICE_UNKNOWN, "DAHDI/I%d/congestion", pri->span); + } +#if defined(THRESHOLD_DEVSTATE_PLACEHOLDER) + /* Update the span threshold device state and report any change. */ + if (in_alarm) { + new_state = AST_DEVICE_UNAVAILABLE; + } else if (!in_use) { + new_state = AST_DEVICE_NOT_INUSE; + } else if (!pri->user_busy_threshold) { + new_state = in_use < num_b_chans ? AST_DEVICE_INUSE : AST_DEVICE_BUSY; + } else { + new_state = in_use < pri->user_busy_threshold ? AST_DEVICE_INUSE + : AST_DEVICE_BUSY; + } + if (pri->threshold_devstate != new_state) { + pri->threshold_devstate = new_state; + ast_devstate_changed(AST_DEVICE_UNKNOWN, "DAHDI/I%d/threshold", pri->span); + } +#endif /* defined(THRESHOLD_DEVSTATE_PLACEHOLDER) */ +} + +/*! + * \internal + * \brief Reference this module. + * \since 1.8 + * + * \return Nothing + */ +static void my_module_ref(void) +{ + ast_module_ref(ast_module_info->self); +} + +/*! + * \internal + * \brief Unreference this module. + * \since 1.8 + * + * \return Nothing + */ +static void my_module_unref(void) +{ + ast_module_unref(ast_module_info->self); +} + static int dahdi_new_pri_nobch_channel(struct sig_pri_pri *pri); static struct sig_pri_callback dahdi_pri_callbacks = @@ -2803,6 +2989,11 @@ static struct sig_pri_callback dahdi_pri_callbacks = .set_dnid = my_pri_set_dnid, .set_rdnis = my_pri_set_rdnis, .new_nobch_intf = dahdi_new_pri_nobch_channel, + .get_orig_dialstring = my_get_orig_dialstring, + .make_cc_dialstring = my_pri_make_cc_dialstring, + .update_span_devstate = dahdi_pri_update_span_devstate, + .module_ref = my_module_ref, + .module_unref = my_module_unref, }; #endif /* defined(HAVE_PRI) */ @@ -2932,6 +3123,7 @@ static struct analog_callback dahdi_analog_callbacks = .cancel_cidspill = my_cancel_cidspill, .confmute = my_confmute, .set_pulsedial = my_set_pulsedial, + .get_orig_dialstring = my_get_orig_dialstring, }; static struct dahdi_pvt *round_robin[32]; @@ -5122,6 +5314,9 @@ static void destroy_dahdi_pvt(struct dahdi_pvt *pvt) if (p->vars) { ast_variables_destroy(p->vars); } + if (p->cc_params) { + ast_cc_config_params_destroy(p->cc_params); + } ast_mutex_destroy(&p->lock); dahdi_close_sub(p, SUB_REAL); if (p->owner) @@ -5957,6 +6152,18 @@ static int dahdi_queryoption(struct ast_channel *chan, int option, void *data, i *cp = (p->callprogress & CALLPROGRESS_FAX) ? 0 : 1; ast_debug(1, "Reporting fax tone detection %sabled on %s\n", *cp ? "en" : "dis", chan->name); break; + case AST_OPTION_CC_AGENT_TYPE: +#if defined(HAVE_PRI) +#if defined(HAVE_PRI_CCSS) + if (dahdi_sig_pri_lib_handles(p->sig)) { + ast_copy_string((char *) data, dahdi_pri_cc_type, *datalen); + break; + } +#endif /* defined(HAVE_PRI_CCSS) */ +#endif /* defined(HAVE_PRI) */ + return -1; + default: + return -1; } errno = 0; @@ -8582,37 +8789,28 @@ static int dahdi_indicate(struct ast_channel *chan, int condition, const void *d return res; } -static struct ast_channel *dahdi_new(struct dahdi_pvt *i, int state, int startpbx, int idx, int law, int transfercapability, const char *linkedid) +#if defined(HAVE_PRI) +static struct ast_str *create_channel_name(struct dahdi_pvt *i, int is_outgoing, char *address) +#else +static struct ast_str *create_channel_name(struct dahdi_pvt *i) +#endif /* defined(HAVE_PRI) */ { - struct ast_channel *tmp; - format_t deflaw; - int res; - int x,y; - int features; struct ast_str *chan_name; - struct ast_variable *v; - struct dahdi_params ps; + int x, y; - if (i->subs[idx].owner) { - ast_log(LOG_WARNING, "Channel %d already has a %s call\n", i->channel,subnames[idx]); + /* Create the new channel name tail. */ + if (!(chan_name = ast_str_create(32))) { return NULL; } - - /* Create the new channel name tail. */ - chan_name = ast_str_alloca(32); if (i->channel == CHAN_PSEUDO) { ast_str_set(&chan_name, 0, "pseudo-%ld", ast_random()); #if defined(HAVE_PRI) } else if (i->pri) { ast_mutex_lock(&i->pri->lock); y = ++i->pri->new_chan_seq; - if (i->outgoing) { - /* - * The dnid has been stuffed with the called-number[:subaddress] - * by dahdi_request(). - */ - ast_str_set(&chan_name, 0, "i%d/%s-%x", i->pri->span, i->dnid, y); - i->dnid[0] = '\0'; + if (is_outgoing) { + ast_str_set(&chan_name, 0, "i%d/%s-%x", i->pri->span, address, y); + address[0] = '\0'; } else if (ast_strlen_zero(i->cid_subaddr)) { /* Put in caller-id number only since there is no subaddress. */ ast_str_set(&chan_name, 0, "i%d/%s-%x", i->pri->span, i->cid_num, y); @@ -8636,11 +8834,49 @@ static struct ast_channel *dahdi_new(struct dahdi_pvt *i, int state, int startpb ++y; } while (x < 3); } + return chan_name; +} + +static struct ast_channel *dahdi_new(struct dahdi_pvt *i, int state, int startpbx, int idx, int law, int transfercapability, const char *linkedid) +{ + struct ast_channel *tmp; + format_t deflaw; + int res; + int x; + int features; + struct ast_str *chan_name; + struct ast_variable *v; + struct dahdi_params ps; + + if (i->subs[idx].owner) { + ast_log(LOG_WARNING, "Channel %d already has a %s call\n", i->channel,subnames[idx]); + return NULL; + } + +#if defined(HAVE_PRI) + /* + * The dnid has been stuffed with the called-number[:subaddress] + * by dahdi_request() for outgoing calls. + */ + chan_name = create_channel_name(i, i->outgoing, i->dnid); +#else + chan_name = create_channel_name(i); +#endif /* defined(HAVE_PRI) */ + if (!chan_name) { + return NULL; + } tmp = ast_channel_alloc(0, state, i->cid_num, i->cid_name, i->accountcode, i->exten, i->context, linkedid, i->amaflags, "DAHDI/%s", ast_str_buffer(chan_name)); + ast_free(chan_name); if (!tmp) return NULL; tmp->tech = &dahdi_tech; +#if defined(HAVE_PRI) + if (i->pri) { + ast_cc_copy_config_params(i->cc_params, i->pri->cc_params); + } +#endif /* defined(HAVE_PRI) */ + ast_channel_cc_params_init(tmp, i->cc_params); memset(&ps, 0, sizeof(ps)); res = ioctl(i->subs[SUB_REAL].dfd, DAHDI_GET_PARAMS, &ps); if (res) { @@ -11169,6 +11405,11 @@ static struct dahdi_pvt *mkintf(int channel, const struct dahdi_chan_conf *conf, if (!tmp) { return NULL; } + tmp->cc_params = ast_cc_config_params_init(); + if (!tmp->cc_params) { + ast_free(tmp); + return NULL; + } ast_mutex_init(&tmp->lock); ifcount++; for (x = 0; x < 3; x++) @@ -11412,6 +11653,16 @@ static struct dahdi_pvt *mkintf(int channel, const struct dahdi_chan_conf *conf, tmp->sig_pvt = pchan; tmp->pri = &pris[span].pri; + if (!tmp->pri->cc_params) { + tmp->pri->cc_params = ast_cc_config_params_init(); + if (!tmp->pri->cc_params) { + destroy_dahdi_pvt(tmp); + return NULL; + } + } + ast_cc_copy_config_params(tmp->pri->cc_params, + conf->chan.cc_params); + pris[span].pri.sig = chan_sig; pris[span].pri.nodetype = conf->pri.pri.nodetype; pris[span].pri.switchtype = myswitchtype; @@ -11434,6 +11685,14 @@ static struct dahdi_pvt *mkintf(int channel, const struct dahdi_chan_conf *conf, pris[span].pri.hold_disconnect_transfer = conf->pri.pri.hold_disconnect_transfer; #endif /* defined(HAVE_PRI_CALL_HOLD) */ +#if defined(HAVE_PRI_CCSS) + pris[span].pri.cc_ptmp_recall_mode = + conf->pri.pri.cc_ptmp_recall_mode; + pris[span].pri.cc_qsig_signaling_link_req = + conf->pri.pri.cc_qsig_signaling_link_req; + pris[span].pri.cc_qsig_signaling_link_rsp = + conf->pri.pri.cc_qsig_signaling_link_rsp; +#endif /* defined(HAVE_PRI_CCSS) */ pris[span].pri.facilityenable = conf->pri.pri.facilityenable; ast_copy_string(pris[span].pri.msn_list, conf->pri.pri.msn_list, sizeof(pris[span].pri.msn_list)); ast_copy_string(pris[span].pri.idledial, conf->pri.pri.idledial, sizeof(pris[span].pri.idledial)); @@ -11742,6 +12001,7 @@ static struct dahdi_pvt *mkintf(int channel, const struct dahdi_chan_conf *conf, tmp->answeronpolarityswitch = conf->chan.answeronpolarityswitch; tmp->hanguponpolarityswitch = conf->chan.hanguponpolarityswitch; tmp->sendcalleridafter = conf->chan.sendcalleridafter; + ast_cc_copy_config_params(tmp->cc_params, conf->chan.cc_params); if (!here) { tmp->locallyblocked = tmp->remotelyblocked = 0; @@ -11881,21 +12141,36 @@ static struct dahdi_pvt *mkintf(int channel, const struct dahdi_chan_conf *conf, return tmp; } -static inline int available(struct dahdi_pvt *p, int channelmatch, ast_group_t groupmatch, int *channelmatched, int *groupmatched) +static int is_group_or_channel_match(struct dahdi_pvt *p, int span, ast_group_t groupmatch, int *groupmatched, int channelmatch, int *channelmatched) { - /* First, check group matching */ +#if defined(HAVE_PRI) + if (0 < span) { + /* The channel must be on the specified PRI span. */ + if (!p->pri || p->pri->span != span) { + return 0; + } + } +#endif /* defined(HAVE_PRI) */ + /* check group matching */ if (groupmatch) { if ((p->group & groupmatch) != groupmatch) + /* Doesn't match the specified group, try the next one */ return 0; *groupmatched = 1; } /* Check to see if we have a channel match */ if (channelmatch != -1) { if (p->channel != channelmatch) + /* Doesn't match the specified channel, try the next one */ return 0; *channelmatched = 1; } + return 1; +} + +static int available(struct dahdi_pvt *p) +{ if (p->inalarm) return 0; @@ -11988,6 +12263,11 @@ static int dahdi_new_pri_nobch_channel(struct sig_pri_pri *pri) if (!pvt) { return -1; } + pvt->cc_params = ast_cc_config_params_init(); + if (!pvt->cc_params) { + ast_free(pvt); + return -1; + } ast_mutex_init(&pvt->lock); for (idx = 0; idx < ARRAY_LEN(pvt->subs); ++idx) { pvt->subs[idx].dfd = -1; @@ -12089,24 +12369,31 @@ static struct dahdi_pvt *duplicate_pseudo(struct dahdi_pvt *src) return p; } -static struct ast_channel *dahdi_request(const char *type, format_t format, const struct ast_channel *requestor, void *data, int *cause) +struct dahdi_starting_point { + /*! Group matching mask. Zero if not specified. */ + ast_group_t groupmatch; + /*! DAHDI channel to match with. -1 if not specified. */ + int channelmatch; + /*! Round robin saved search location index. (Valid if roundrobin TRUE) */ + int rr_starting_point; + /*! ISDN span where channels can be picked (Zero if not specified) */ + int span; + /*! Analog channel distinctive ring cadance index. */ + int cadance; + /*! Dialing option. c/r/d if present and valid. */ + char opt; + /*! TRUE if to search the channel list backwards. */ + char backwards; + /*! TRUE if search is done with round robin sequence. */ + char roundrobin; +}; +static struct dahdi_pvt *determine_starting_point(const char *data, struct dahdi_starting_point *param) { - ast_group_t groupmatch = 0; - int channelmatch = -1; - int roundrobin = 0; - int callwait = 0; - struct dahdi_pvt *p; - struct ast_channel *tmp = NULL; char *dest; - int x; char *s; - char opt=0; - int res=0, y=0; - int backwards = 0; - struct dahdi_pvt *exitpvt; - int channelmatched = 0; - int groupmatched = 0; - int transcapdigital = 0; + int x; + int res = 0; + struct dahdi_pvt *p; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(group); /* channel/group token */ //AST_APP_ARG(ext); /* extension token */ @@ -12117,8 +12404,11 @@ static struct ast_channel *dahdi_request(const char *type, format_t format, cons /* * data is ---v * Dial(DAHDI/pseudo[/extension[/options]]) - * Dial(DAHDI/[c|r|d][/extension[/options]]) - * Dial(DAHDI/(g|G|r|R)[c|r|d][/extension[/options]]) + * Dial(DAHDI/[i-][c|r|d][/extension[/options]]) + * Dial(DAHDI/[i-](g|G|r|R)[c|r|d][/extension[/options]]) + * + * i - ISDN span channel restriction. + * Used by CC to ensure that the CC recall goes out the same span. * * g - channel group allocation search forward * G - channel group allocation search backward @@ -12131,7 +12421,7 @@ static struct ast_channel *dahdi_request(const char *type, format_t format, cons */ if (data) { - dest = ast_strdupa((char *)data); + dest = ast_strdupa(data); } else { ast_log(LOG_WARNING, "Channel requested with no data\n"); return NULL; @@ -12142,27 +12432,47 @@ static struct ast_channel *dahdi_request(const char *type, format_t format, cons return NULL; } + /* Initialize the output parameters */ + memset(param, 0, sizeof(*param)); + param->channelmatch = -1; + + if (args.group[0] == 'i') { + /* Extract the ISDN span channel restriction specifier. */ + res = sscanf(args.group + 1, "%30d", &x); + if (res < 1) { + ast_log(LOG_WARNING, "Unable to determine ISDN span for data %s\n", data); + return NULL; + } + param->span = x; + + /* Remove the ISDN span channel restriction specifier. */ + s = strchr(args.group, '-'); + if (!s) { + ast_log(LOG_WARNING, "Bad ISDN span format for data %s\n", data); + return NULL; + } + args.group = s + 1; + res = 0; + } if (toupper(args.group[0]) == 'G' || toupper(args.group[0])=='R') { /* Retrieve the group number */ s = args.group + 1; - if ((res = sscanf(s, "%30d%1c%30d", &x, &opt, &y)) < 1) { - ast_log(LOG_WARNING, "Unable to determine group for data %s\n", (char *)data); + res = sscanf(s, "%30d%1c%30d", &x, ¶m->opt, ¶m->cadance); + if (res < 1) { + ast_log(LOG_WARNING, "Unable to determine group for data %s\n", data); return NULL; } - groupmatch = ((ast_group_t) 1 << x); - - /* Lock the interface list */ - ast_mutex_lock(&iflock); + param->groupmatch = ((ast_group_t) 1 << x); if (toupper(args.group[0]) == 'G') { if (args.group[0] == 'G') { - backwards = 1; + param->backwards = 1; p = ifend; } else p = iflist; } else { if (args.group[0] == 'R') { - backwards = 1; + param->backwards = 1; p = round_robin[x]?round_robin[x]->prev:ifend; if (!p) p = ifend; @@ -12171,36 +12481,62 @@ static struct ast_channel *dahdi_request(const char *type, format_t format, cons if (!p) p = iflist; } - roundrobin = 1; + param->roundrobin = 1; + param->rr_starting_point = x; } } else { s = args.group; if (!strcasecmp(s, "pseudo")) { /* Special case for pseudo */ x = CHAN_PSEUDO; - channelmatch = x; - } else if ((res = sscanf(s, "%30d%1c%30d", &x, &opt, &y)) < 1) { - ast_log(LOG_WARNING, "Unable to determine channel for data %s\n", (char *)data); - return NULL; + param->channelmatch = x; } else { - channelmatch = x; + res = sscanf(s, "%30d%1c%30d", &x, ¶m->opt, ¶m->cadance); + if (res < 1) { + ast_log(LOG_WARNING, "Unable to determine channel for data %s\n", data); + return NULL; + } else { + param->channelmatch = x; + } } - /* Lock the interface list */ - ast_mutex_lock(&iflock); - p = iflist; } + + if (param->opt == 'r' && res < 3) { + ast_log(LOG_WARNING, "Distinctive ring missing identifier in '%s'\n", data); + param->opt = '\0'; + } + + return p; +} + +static struct ast_channel *dahdi_request(const char *type, format_t format, const struct ast_channel *requestor, void *data, int *cause) +{ + int callwait = 0; + struct dahdi_pvt *p; + struct ast_channel *tmp = NULL; + struct dahdi_pvt *exitpvt; + int channelmatched = 0; + int groupmatched = 0; + int transcapdigital = 0; + struct dahdi_starting_point start; + + p = determine_starting_point(data, &start); + if (!p) { + /* We couldn't determine a starting point, which likely means badly-formatted channel name. Abort! */ + return NULL; + } + /* Search for an unowned channel */ exitpvt = p; + ast_mutex_lock(&iflock); while (p && !tmp) { - if (roundrobin) - round_robin[x] = p; -#if 0 - ast_verbose("name = %s, %d, %d, %llu\n",p->owner ? p->owner->name : "", p->channel, channelmatch, groupmatch); -#endif + if (start.roundrobin) + round_robin[start.rr_starting_point] = p; - if (p && available(p, channelmatch, groupmatch, &channelmatched, &groupmatched)) { + if (is_group_or_channel_match(p, start.span, start.groupmatch, &groupmatched, start.channelmatch, &channelmatched) + && available(p)) { ast_debug(1, "Using channel %d\n", p->channel); callwait = (p->owner != NULL); @@ -12224,22 +12560,25 @@ static struct ast_channel *dahdi_request(const char *type, format_t format, cons } /* Make special notes */ - if (res > 1) { - if (opt == 'c') { - /* Confirm answer */ - p->confirmanswer = 1; - } else if (opt == 'r') { - /* Distinctive ring */ - if (res < 3) - ast_log(LOG_WARNING, "Distinctive ring missing identifier in '%s'\n", (char *)data); - else - p->distinctivering = y; - } else if (opt == 'd') { - /* If this is an ISDN call, make it digital */ - transcapdigital = AST_TRANS_CAP_DIGITAL; - } else { - ast_log(LOG_WARNING, "Unknown option '%c' in '%s'\n", opt, (char *)data); - } + switch (start.opt) { + case '\0': + /* No option present. */ + break; + case 'c': + /* Confirm answer */ + p->confirmanswer = 1; + break; + case 'r': + /* Distinctive ring */ + p->distinctivering = start.cadance; + break; + case 'd': + /* If this is an ISDN call, make it digital */ + transcapdigital = AST_TRANS_CAP_DIGITAL; + break; + default: + ast_log(LOG_WARNING, "Unknown option '%c' in '%s'\n", start.opt, (char *)data); + break; } p->outgoing = 1; @@ -12256,13 +12595,15 @@ static struct ast_channel *dahdi_request(const char *type, format_t format, cons } if (!tmp) { p->outgoing = 0; + } else { + snprintf(p->dialstring, sizeof(p->dialstring), "DAHDI/%s", (char *) data); } break; } #ifdef HAVE_OPENR2 next: #endif - if (backwards) { + if (start.backwards) { p = p->prev; if (!p) p = ifend; @@ -12293,6 +12634,167 @@ next: return tmp; } +/*! + * \internal + * \brief Determine the device state for a given DAHDI device if we can. + * \since 1.8 + * + * \param data DAHDI device name after "DAHDI/". + * + * \retval device_state enum ast_device_state value. + * \retval AST_DEVICE_UNKNOWN if we could not determine the device's state. + */ +static int dahdi_devicestate(void *data) +{ +#if defined(HAVE_PRI) + char *device; + unsigned span; + int res; + + device = data; + + if (*device != 'I') { + /* The request is not for an ISDN span device. */ + return AST_DEVICE_UNKNOWN; + } + res = sscanf(device, "I%30u", &span); + if (res != 1 || !span || NUM_SPANS < span) { + /* Bad format for ISDN span device name. */ + return AST_DEVICE_UNKNOWN; + } + device = strchr(device, '/'); + if (!device) { + /* Bad format for ISDN span device name. */ + return AST_DEVICE_UNKNOWN; + } + + /* + * Since there are currently no other span devstate's defined, + * it must be congestion. + */ +#if defined(THRESHOLD_DEVSTATE_PLACEHOLDER) + ++device; + if (!strcmp(device, "congestion")) +#endif /* defined(THRESHOLD_DEVSTATE_PLACEHOLDER) */ + { + return pris[span - 1].pri.congestion_devstate; + } +#if defined(THRESHOLD_DEVSTATE_PLACEHOLDER) + else if (!strcmp(device, "threshold")) { + return pris[span - 1].pri.threshold_devstate; + } + return AST_DEVICE_UNKNOWN; +#endif /* defined(THRESHOLD_DEVSTATE_PLACEHOLDER) */ +#else + return AST_DEVICE_UNKNOWN; +#endif /* defined(HAVE_PRI) */ +} + +/*! + * \brief Callback made when dial failed to get a channel out of dahdi_request(). + * \since 1.8 + * + * \param inbound Incoming asterisk channel. + * \param dest Same dial string passed to dahdi_request(). + * \param callback Callback into CC core to announce a busy channel available for CC. + * + * \details + * This callback acts like a forked dial with all prongs of the fork busy. + * Essentially, for each channel that could have taken the call, indicate that + * it is busy. + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int dahdi_cc_callback(struct ast_channel *inbound, const char *dest, ast_cc_callback_fn callback) +{ + struct dahdi_pvt *p; + struct dahdi_pvt *exitpvt; + struct dahdi_starting_point start; + int groupmatched = 0; + int channelmatched = 0; + + p = determine_starting_point(dest, &start); + if (!p) { + return -1; + } + ast_mutex_lock(&iflock); + exitpvt = p; + for (;;) { + if (is_group_or_channel_match(p, start.span, start.groupmatch, &groupmatched, start.channelmatch, &channelmatched)) { + /* We found a potential match. call the callback */ + struct ast_str *device_name; + char *dash; + const char *monitor_type; + char dialstring[AST_CHANNEL_NAME]; + char full_device_name[AST_CHANNEL_NAME]; + + switch (ast_get_cc_monitor_policy(p->cc_params)) { + case AST_CC_MONITOR_NEVER: + break; + case AST_CC_MONITOR_NATIVE: + case AST_CC_MONITOR_ALWAYS: + case AST_CC_MONITOR_GENERIC: +#if defined(HAVE_PRI) + if (dahdi_sig_pri_lib_handles(p->sig)) { + /* + * ISDN is in a trunk busy condition so we need to monitor + * the span congestion device state. + */ + snprintf(full_device_name, sizeof(full_device_name), + "DAHDI/I%d/congestion", p->pri->span); + } else +#endif /* defined(HAVE_PRI) */ + { +#if defined(HAVE_PRI) + device_name = create_channel_name(p, 1, ""); +#else + device_name = create_channel_name(p); +#endif /* defined(HAVE_PRI) */ + snprintf(full_device_name, sizeof(full_device_name), "DAHDI/%s", + device_name ? ast_str_buffer(device_name) : ""); + ast_free(device_name); + /* + * The portion after the '-' in the channel name is either a random + * number, a sequence number, or a subchannel number. None are + * necessary so strip them off. + */ + dash = strrchr(full_device_name, '-'); + if (dash) { + *dash = '\0'; + } + } + snprintf(dialstring, sizeof(dialstring), "DAHDI/%s", dest); + + /* + * Analog can only do generic monitoring. + * ISDN is in a trunk busy condition and any "device" is going + * to be busy until a B channel becomes available. The generic + * monitor can do this task. + */ + monitor_type = AST_CC_GENERIC_MONITOR_TYPE; + callback(inbound, +#if defined(HAVE_PRI) + p->pri ? p->pri->cc_params : p->cc_params, +#else + p->cc_params, +#endif /* defined(HAVE_PRI) */ + monitor_type, full_device_name, dialstring, NULL); + break; + } + } + p = start.backwards ? p->prev : p->next; + if (!p) { + p = start.backwards ? ifend : iflist; + } + if (p == exitpvt) { + break; + } + } + ast_mutex_unlock(&iflock); + return 0; +} + #if defined(HAVE_SS7) static int ss7_find_cic(struct dahdi_ss7 *linkset, int cic, unsigned int dpc) { @@ -13480,9 +13982,7 @@ static char *handle_pri_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_a for (x = 0; x < NUM_DCHANS; x++) { if (pris[span-1].pri.dchans[x]) { if (level == 1) { - pri_set_debug(pris[span-1].pri.dchans[x], PRI_DEBUG_APDU | - PRI_DEBUG_Q931_DUMP | PRI_DEBUG_Q931_STATE | - PRI_DEBUG_Q921_STATE); + pri_set_debug(pris[span-1].pri.dchans[x], SIG_PRI_DEBUG_NORMAL); ast_cli(a->fd, "Enabled debugging on span %d\n", span); } else if (level == 0) { pri_set_debug(pris[span-1].pri.dchans[x], 0); @@ -13493,9 +13993,7 @@ static char *handle_pri_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_a ast_cli(a->fd, "PRI debug output to file disabled\n"); ast_mutex_unlock(&pridebugfdlock); } else { - pri_set_debug(pris[span-1].pri.dchans[x], PRI_DEBUG_APDU | - PRI_DEBUG_Q931_DUMP | PRI_DEBUG_Q931_STATE | - PRI_DEBUG_Q921_RAW | PRI_DEBUG_Q921_DUMP | PRI_DEBUG_Q921_STATE); + pri_set_debug(pris[span-1].pri.dchans[x], SIG_PRI_DEBUG_INTENSE); ast_cli(a->fd, "Enabled debugging on span %d\n", span); } } @@ -13583,6 +14081,8 @@ static char *handle_pri_service_generic(struct ast_cli_entry *e, int cmd, struct if (*why) { snprintf(db_answer, sizeof(db_answer), "%s:%u", SRVST_TYPE_OOS, *why); ast_db_put(db_chan_name, SRVST_DBKEY, db_answer); + } else { + dahdi_pri_update_span_devstate(tmp->pri); } break; /* case 1: -- loop */ @@ -13592,6 +14092,7 @@ static char *handle_pri_service_generic(struct ast_cli_entry *e, int cmd, struct *why |= SRVST_NEAREND; snprintf(db_answer, sizeof(db_answer), "%s:%u", SRVST_TYPE_OOS, *why); ast_db_put(db_chan_name, SRVST_DBKEY, db_answer); + dahdi_pri_update_span_devstate(tmp->pri); break; /* case 3: -- continuity */ /* case 4: -- shutdown */ @@ -15612,6 +16113,110 @@ static struct ast_cli_entry dahdi_ss7_cli[] = { }; #endif /* defined(HAVE_SS7) */ +#if defined(HAVE_PRI) +#if defined(HAVE_PRI_CCSS) +/*! + * \internal + * \brief CC agent initialization. + * \since 1.8 + * + * \param agent CC core agent control. + * \param chan Original channel the agent will attempt to recall. + * + * \details + * This callback is called when the CC core is initialized. Agents should allocate + * any private data necessary for the call and assign it to the private_data + * on the agent. Additionally, if any ast_cc_agent_flags are pertinent to the + * specific agent type, they should be set in this function as well. + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int dahdi_pri_cc_agent_init(struct ast_cc_agent *agent, struct ast_channel *chan) +{ + struct dahdi_pvt *pvt; + struct sig_pri_chan *pvt_chan; + int res; + + ast_assert(!strcmp(chan->tech->type, "DAHDI")); + + pvt = chan->tech_pvt; + if (dahdi_sig_pri_lib_handles(pvt->sig)) { + pvt_chan = pvt->sig_pvt; + } else { + pvt_chan = NULL; + } + if (!pvt_chan) { + return -1; + } + + ast_module_ref(ast_module_info->self); + + res = sig_pri_cc_agent_init(agent, pvt_chan); + if (res) { + ast_module_unref(ast_module_info->self); + } + return res; +} +#endif /* defined(HAVE_PRI_CCSS) */ +#endif /* defined(HAVE_PRI) */ + +#if defined(HAVE_PRI) +#if defined(HAVE_PRI_CCSS) +/*! + * \internal + * \brief Destroy private data on the agent. + * \since 1.8 + * + * \param agent CC core agent control. + * + * \details + * The core will call this function upon completion + * or failure of CC. + * + * \return Nothing + */ +static void dahdi_pri_cc_agent_destructor(struct ast_cc_agent *agent) +{ + sig_pri_cc_agent_destructor(agent); + + ast_module_unref(ast_module_info->self); +} +#endif /* defined(HAVE_PRI_CCSS) */ +#endif /* defined(HAVE_PRI) */ + +#if defined(HAVE_PRI) +#if defined(HAVE_PRI_CCSS) +static struct ast_cc_agent_callbacks dahdi_pri_cc_agent_callbacks = { + .type = dahdi_pri_cc_type, + .init = dahdi_pri_cc_agent_init, + .start_offer_timer = sig_pri_cc_agent_start_offer_timer, + .stop_offer_timer = sig_pri_cc_agent_stop_offer_timer, + .ack = sig_pri_cc_agent_req_ack, + .status_request = sig_pri_cc_agent_status_req, + .stop_ringing = sig_pri_cc_agent_stop_ringing, + .party_b_free = sig_pri_cc_agent_party_b_free, + .start_monitoring = sig_pri_cc_agent_start_monitoring, + .callee_available = sig_pri_cc_agent_callee_available, + .destructor = dahdi_pri_cc_agent_destructor, +}; +#endif /* defined(HAVE_PRI_CCSS) */ +#endif /* defined(HAVE_PRI) */ + +#if defined(HAVE_PRI) +#if defined(HAVE_PRI_CCSS) +static struct ast_cc_monitor_callbacks dahdi_pri_cc_monitor_callbacks = { + .type = dahdi_pri_cc_type, + .request_cc = sig_pri_cc_monitor_req_cc, + .suspend = sig_pri_cc_monitor_suspend, + .unsuspend = sig_pri_cc_monitor_unsuspend, + .status_response = sig_pri_cc_monitor_status_rsp, + .cancel_available_timer = sig_pri_cc_monitor_cancel_available_timer, + .destructor = sig_pri_cc_monitor_destructor, +}; +#endif /* defined(HAVE_PRI_CCSS) */ +#endif /* defined(HAVE_PRI) */ + static int __unload_module(void) { struct dahdi_pvt *p; @@ -15680,6 +16285,11 @@ static int __unload_module(void) dahdi_close_pri_fd(&(pris[i]), j); } } +#if defined(HAVE_PRI_CCSS) + ast_cc_agent_unregister(&dahdi_pri_cc_agent_callbacks); + ast_cc_monitor_unregister(&dahdi_pri_cc_monitor_callbacks); +#endif /* defined(HAVE_PRI_CCSS) */ + sig_pri_unload(); #endif #if defined(HAVE_SS7) @@ -16100,6 +16710,8 @@ static int process_dahdi(struct dahdi_chan_conf *confp, const char *cat, struct confp->chan.sendcalleridafter = atoi(v->value); } else if (!strcasecmp(v->name, "mwimonitornotify")) { ast_copy_string(mwimonitornotify, v->value, sizeof(mwimonitornotify)); + } else if (ast_cc_is_config_param(v->name)) { + ast_cc_set_param(confp->chan.cc_params, v->name, v->value); } else if (!strcasecmp(v->name, "mwisendtype")) { #ifndef HAVE_DAHDI_LINEREVERSE_VMWI /* backward compatibility for older dahdi VMWI implementation */ if (!strcasecmp(v->value, "rpas")) { /* Ring Pulse Alert Signal */ @@ -16478,6 +17090,34 @@ static int process_dahdi(struct dahdi_chan_conf *confp, const char *cat, struct } else if (!strcasecmp(v->name, "hold_disconnect_transfer")) { confp->pri.pri.hold_disconnect_transfer = ast_true(v->value); #endif /* defined(HAVE_PRI_CALL_HOLD) */ +#if defined(HAVE_PRI_CCSS) + } else if (!strcasecmp(v->name, "cc_ptmp_recall_mode")) { + if (!strcasecmp(v->value, "global")) { + confp->pri.pri.cc_ptmp_recall_mode = 0;/* globalRecall */ + } else if (!strcasecmp(v->value, "specific")) { + confp->pri.pri.cc_ptmp_recall_mode = 1;/* specificRecall */ + } else { + confp->pri.pri.cc_ptmp_recall_mode = 1;/* specificRecall */ + } + } else if (!strcasecmp(v->name, "cc_qsig_signaling_link_req")) { + if (!strcasecmp(v->value, "release")) { + confp->pri.pri.cc_qsig_signaling_link_req = 0;/* release */ + } else if (!strcasecmp(v->value, "retain")) { + confp->pri.pri.cc_qsig_signaling_link_req = 1;/* retain */ + } else if (!strcasecmp(v->value, "do_not_care")) { + confp->pri.pri.cc_qsig_signaling_link_req = 2;/* do-not-care */ + } else { + confp->pri.pri.cc_qsig_signaling_link_req = 1;/* retain */ + } + } else if (!strcasecmp(v->name, "cc_qsig_signaling_link_rsp")) { + if (!strcasecmp(v->value, "release")) { + confp->pri.pri.cc_qsig_signaling_link_rsp = 0;/* release */ + } else if (!strcasecmp(v->value, "retain")) { + confp->pri.pri.cc_qsig_signaling_link_rsp = 1;/* retain */ + } else { + confp->pri.pri.cc_qsig_signaling_link_rsp = 1;/* retain */ + } +#endif /* defined(HAVE_PRI_CCSS) */ #endif /* HAVE_PRI */ #ifdef HAVE_SS7 } else if (!strcasecmp(v->name, "ss7type")) { @@ -16800,23 +17440,57 @@ static int process_dahdi(struct dahdi_chan_conf *confp, const char *cat, struct */ struct dahdi_chan_conf conf = dahdi_chan_conf_default(); - tmp = mkintf(CHAN_PSEUDO, &conf, reload); - + if (conf.chan.cc_params) { + tmp = mkintf(CHAN_PSEUDO, &conf, reload); + } else { + tmp = NULL; + } if (tmp) { ast_verb(3, "Automatically generated pseudo channel\n"); } else { ast_log(LOG_WARNING, "Unable to register pseudo channel!\n"); } + ast_cc_config_params_destroy(conf.chan.cc_params); } return 0; } -static int setup_dahdi(int reload) +/*! + * \internal + * \brief Deep copy struct dahdi_chan_conf. + * \since 1.8 + * + * \param dest Destination. + * \param src Source. + * + * \return Nothing + */ +static void deep_copy_dahdi_chan_conf(struct dahdi_chan_conf *dest, const struct dahdi_chan_conf *src) +{ + struct ast_cc_config_params *cc_params; + + cc_params = dest->chan.cc_params; + memcpy(dest, src, sizeof(dest)); + dest->chan.cc_params = cc_params; + ast_cc_copy_config_params(dest->chan.cc_params, src->chan.cc_params); +} + +/*! + * \internal + * \brief Setup DAHDI channel driver. + * + * \param reload enum: load_module(0), reload(1), restart(2). + * \param base_conf Default config parameters. So cc_params can be properly destroyed. + * \param conf Local config parameters. So cc_params can be properly destroyed. + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int setup_dahdi_int(int reload, struct dahdi_chan_conf *base_conf, struct dahdi_chan_conf *conf) { - struct ast_config *cfg, *ucfg; + struct ast_config *cfg; + struct ast_config *ucfg; struct ast_variable *v; - struct dahdi_chan_conf base_conf = dahdi_chan_conf_default(); - struct dahdi_chan_conf conf; struct ast_flags config_flags = { reload == 1 ? CONFIG_FLAG_FILEUNCHANGED : 0 }; const char *cat; int res; @@ -16931,7 +17605,7 @@ static int setup_dahdi(int reload) mwimonitornotify[0] = '\0'; v = ast_variable_browse(cfg, "channels"); - if ((res = process_dahdi(&base_conf, "", v, reload, 0))) { + if ((res = process_dahdi(base_conf, "", v, reload, 0))) { ast_mutex_unlock(&iflock); ast_config_destroy(cfg); if (ucfg) { @@ -16952,9 +17626,10 @@ static int setup_dahdi(int reload) continue; } - memcpy(&conf, &base_conf, sizeof(conf)); + /* Copy base_conf to conf. */ + deep_copy_dahdi_chan_conf(conf, base_conf); - if ((res = process_dahdi(&conf, cat, ast_variable_browse(cfg, cat), reload, PROC_DAHDI_OPT_NOCHAN))) { + if ((res = process_dahdi(conf, cat, ast_variable_browse(cfg, cat), reload, PROC_DAHDI_OPT_NOCHAN))) { ast_mutex_unlock(&iflock); ast_config_destroy(cfg); if (ucfg) { @@ -16969,7 +17644,7 @@ static int setup_dahdi(int reload) if (ucfg) { const char *chans; - process_dahdi(&base_conf, "", ast_variable_browse(ucfg, "general"), 1, 0); + process_dahdi(base_conf, "", ast_variable_browse(ucfg, "general"), 1, 0); for (cat = ast_category_browse(ucfg, NULL); cat ; cat = ast_category_browse(ucfg, cat)) { if (!strcasecmp(cat, "general")) { @@ -16982,9 +17657,10 @@ static int setup_dahdi(int reload) continue; } - memcpy(&conf, &base_conf, sizeof(conf)); + /* Copy base_conf to conf. */ + deep_copy_dahdi_chan_conf(conf, base_conf); - if ((res = process_dahdi(&conf, cat, ast_variable_browse(ucfg, cat), reload, PROC_DAHDI_OPT_NOCHAN | PROC_DAHDI_OPT_NOWARN))) { + if ((res = process_dahdi(conf, cat, ast_variable_browse(ucfg, cat), reload, PROC_DAHDI_OPT_NOCHAN | PROC_DAHDI_OPT_NOWARN))) { ast_config_destroy(ucfg); ast_mutex_unlock(&iflock); return res; @@ -17041,6 +17717,32 @@ static int setup_dahdi(int reload) return 0; } +/*! + * \internal + * \brief Setup DAHDI channel driver. + * + * \param reload enum: load_module(0), reload(1), restart(2). + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int setup_dahdi(int reload) +{ + int res; + struct dahdi_chan_conf base_conf = dahdi_chan_conf_default(); + struct dahdi_chan_conf conf = dahdi_chan_conf_default(); + + if (base_conf.chan.cc_params && conf.chan.cc_params) { + res = setup_dahdi_int(reload, &base_conf, &conf); + } else { + res = -1; + } + ast_cc_config_params_destroy(base_conf.chan.cc_params); + ast_cc_config_params_destroy(conf.chan.cc_params); + + return res; +} + static int load_module(void) { int res; @@ -17061,6 +17763,23 @@ static int load_module(void) #ifdef HAVE_PRI_PROG_W_CAUSE ast_register_application_xml(dahdi_send_callrerouting_facility_app, dahdi_send_callrerouting_facility_exec); #endif +#if defined(HAVE_PRI_CCSS) + if (ast_cc_agent_register(&dahdi_pri_cc_agent_callbacks) + || ast_cc_monitor_register(&dahdi_pri_cc_monitor_callbacks)) { + __unload_module(); + return AST_MODULE_LOAD_FAILURE; + } +#endif /* defined(HAVE_PRI_CCSS) */ + if (sig_pri_load( +#if defined(HAVE_PRI_CCSS) + dahdi_pri_cc_type +#else + NULL +#endif /* defined(HAVE_PRI_CCSS) */ + )) { + __unload_module(); + return AST_MODULE_LOAD_FAILURE; + } #endif #ifdef HAVE_SS7 memset(linksets, 0, sizeof(linksets)); diff --git a/channels/chan_local.c b/channels/chan_local.c index 5e522e797..b8052b0fd 100644 --- a/channels/chan_local.c +++ b/channels/chan_local.c @@ -545,6 +545,8 @@ static int local_call(struct ast_channel *ast, char *dest, int timeout) int res; struct ast_var_t *varptr = NULL, *new; size_t len, namelen; + char *reduced_dest = ast_strdupa(dest); + char *slash; if (!p) return -1; @@ -594,6 +596,8 @@ start_over: ast_string_field_set(p->chan, musicclass, p->owner->musicclass); ast_cdr_update(p->chan); + ast_channel_cc_params_init(p->chan, ast_channel_get_cc_config_params(p->owner)); + if (!ast_exists_extension(NULL, p->chan->context, p->chan->exten, 1, p->owner->cid.cid_num)) { ast_log(LOG_NOTICE, "No such extension/context %s@%s while calling Local channel\n", p->chan->exten, p->chan->context); ast_mutex_unlock(&p->lock); @@ -618,6 +622,14 @@ start_over: } } ast_channel_datastore_inherit(p->owner, p->chan); + /* If the local channel has /n or /b on the end of it, + * we need to lop that off for our argument to setting + * up the CC_INTERFACES variable + */ + if ((slash = strrchr(reduced_dest, '/'))) { + *slash = '\0'; + } + ast_set_cc_interfaces_chanvar(p->chan, reduced_dest); /* Start switch on sub channel */ if (!(res = ast_pbx_start(p->chan))) @@ -857,6 +869,10 @@ static struct ast_channel *local_request(const char *type, format_t format, cons AST_LIST_UNLOCK(&locals); p = local_pvt_destroy(p); } + if (ast_channel_cc_params_init(chan, ast_channel_get_cc_config_params((struct ast_channel *)requestor))) { + chan = ast_channel_release(chan); + p = local_pvt_destroy(p); + } } return chan; diff --git a/channels/chan_sip.c b/channels/chan_sip.c index bd6cb1889..91773b05c 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -266,6 +266,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #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" @@ -625,7 +627,7 @@ static const struct cfsip_methods { { 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 } }; @@ -784,6 +786,14 @@ static int global_max_se; /*!< Highest threshold for session 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. */ @@ -851,6 +861,251 @@ static const int HASH_PEER_SIZE = 563; /*!< Size of peer hash table, prime numbe 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 @@ -1001,6 +1256,7 @@ static int sip_prepare_socket(struct sip_pvt *p); 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); @@ -1015,7 +1271,8 @@ static int transmit_response_with_allow(struct sip_pvt *p, const char *msg, cons 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); @@ -1023,6 +1280,7 @@ static int transmit_message_with_text(struct sip_pvt *p, const char *text); 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); @@ -1236,7 +1494,7 @@ static int set_address_from_contact(struct sip_pvt *pvt); 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); @@ -1253,7 +1511,7 @@ static void *sip_tcp_worker_fn(void *); 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); @@ -1297,6 +1555,7 @@ static int handle_request_notify(struct sip_pvt *p, struct sip_request *req, str 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); @@ -1372,3195 +1631,3779 @@ const struct ast_channel_tech sip_tech = { */ 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, "\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, "\n", presentity); + ast_str_append(&body, 0, "\n", tuple_id); + ast_str_append(&body, 0, "%s\n", state == CC_OPEN ? "open" : "closed"); + ast_str_append(&body, 0, "\n"); + ast_str_append(&body, 0, "\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 : ""); - 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 : ""); - 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 : ""); + 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 : ""); + 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 : ""); + 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 : ""); - 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; @@ -5446,7 +6289,10 @@ static struct ast_channel *sip_new(struct sip_pvt *i, int state, const char *tit 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; @@ -5911,6 +6757,23 @@ static char *generate_random_string(char *buf, size_t size) 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) { @@ -5975,6 +6838,11 @@ struct sip_pvt *sip_alloc(ast_string_field callid, struct sockaddr_in *sin, 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 { @@ -8234,6 +9102,9 @@ static int __transmit_response(struct sip_pvt *p, const char *msg, const struct 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 @@ -8269,6 +9140,19 @@ static int __transmit_response(struct sip_pvt *p, const char *msg, const struct 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; @@ -9305,6 +10189,38 @@ static void copy_request(struct sip_request *dst, const struct sip_request *src) 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. */ @@ -9320,6 +10236,9 @@ static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const 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"); @@ -9479,7 +10398,7 @@ static void build_contact(struct sip_pvt *p) } /*! \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]; @@ -9564,25 +10483,30 @@ static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmetho else snprintf(from, sizeof(from), "\"%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 */ @@ -9664,14 +10588,47 @@ static void add_diversion_header(struct sip_request *req, struct sip_pvt *pvt) 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), ";reason=%s", diverting_number, ast_inet_ntoa(pvt->ourip.sin_addr), reason); - } else { - snprintf(header_text, sizeof(header_text), "\"%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), ";reason=%s", diverting_number, ast_inet_ntoa(pvt->ourip.sin_addr), reason); + } else { + snprintf(header_text, sizeof(header_text), "\"%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 @@ -9681,7 +10638,7 @@ static void add_diversion_header(struct sip_request *req, struct sip_pvt *pvt) \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; @@ -9693,7 +10650,7 @@ static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init) 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); @@ -9711,12 +10668,16 @@ static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init) 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); } @@ -9805,13 +10766,33 @@ static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init) 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); @@ -9845,7 +10826,7 @@ static int __sip_subscribe_mwi_do(struct sip_subscription_mwi *mwi) /* 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; } @@ -9866,6 +10847,8 @@ static int __sip_subscribe_mwi_do(struct sip_subscription_mwi *mwi) 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); @@ -9900,7 +10883,7 @@ static int __sip_subscribe_mwi_do(struct sip_subscription_mwi *mwi) 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; } @@ -10084,6 +11067,34 @@ static void state_notify_build_xml(int state, int full, const char *exten, const } } +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) @@ -10183,7 +11194,7 @@ static int transmit_notify_with_mwi(struct sip_pvt *p, int newmsgs, int oldmsgs, 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"); @@ -10297,7 +11308,7 @@ static int manager_sipnotify(struct mansession *s, const struct message *m) 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); @@ -12438,13 +13449,13 @@ static int get_rdnis(struct sip_pvt *p, struct sip_request *oreq, char **name, c 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; @@ -12453,7 +13464,7 @@ static int get_destination(struct sip_pvt *p, struct sip_request *oreq) 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); @@ -12510,6 +13521,7 @@ static int get_destination(struct sip_pvt *p, struct sip_request *oreq) 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, @@ -12522,6 +13534,22 @@ static int get_destination(struct sip_pvt *p, struct sip_request *oreq) 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; } } @@ -13033,6 +14061,7 @@ static enum check_auth_result check_peer_ok(struct sip_pvt *p, char *of, 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) @@ -15981,7 +17010,7 @@ static char *sip_cli_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_arg 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; @@ -16061,7 +17090,7 @@ static int do_proxy_auth(struct sip_pvt *p, struct sip_request *req, enum sip_au /* 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 @@ -16698,6 +17727,106 @@ static void handle_response_update(struct sip_pvt *p, int resp, const char *rest } } +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) { @@ -16769,6 +17898,7 @@ static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest 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); @@ -16794,6 +17924,7 @@ static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest 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; @@ -16811,6 +17942,7 @@ static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest 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) @@ -17569,6 +18701,11 @@ static void handle_response(struct sip_pvt *p, int resp, const char *rest, struc 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 */ @@ -17763,8 +18900,10 @@ static void handle_response(struct sip_pvt *p, int resp, const char *rest, struc 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 @@ -18214,6 +19353,66 @@ static const char *gettag(const struct sip_request *req, const char *header, cha 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) { @@ -18376,6 +19575,8 @@ static int handle_request_notify(struct sip_pvt *p, struct sip_request *req, str /* 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); @@ -18411,7 +19612,7 @@ static int handle_request_options(struct sip_pvt *p, struct sip_request *req) return 0; } - res = get_destination(p, req); + res = get_destination(p, req, NULL); build_contact(p); if (ast_strlen_zero(p->context)) @@ -19271,6 +20472,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int /* 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) { @@ -19338,7 +20540,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int 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 */ @@ -19376,6 +20578,10 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int 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 */ @@ -19444,1014 +20650,1504 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int } } - 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 : ""); + + 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 : ""); + 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) @@ -20466,6 +22162,63 @@ 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) { @@ -20578,9 +22331,9 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, 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 */ @@ -20685,6 +22438,8 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, 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); @@ -20695,7 +22450,7 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, } /* 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 */ @@ -20716,10 +22471,13 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, 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"); @@ -20734,7 +22492,7 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, 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) { @@ -21005,7 +22763,7 @@ static int handle_incoming(struct sip_pvt *p, struct sip_request *req, struct so } } - 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; @@ -21031,6 +22789,9 @@ static int handle_incoming(struct sip_pvt *p, struct sip_request *req, struct so 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; @@ -22001,7 +23762,7 @@ static void proc_422_rsp(struct sip_pvt *p, struct sip_request *rsp) return; } p->stimer->st_interval = minse; - transmit_invite(p, SIP_INVITE, 1, 2); + transmit_invite(p, SIP_INVITE, 1, 2, NULL); } @@ -22195,9 +23956,9 @@ static int sip_poke_peer(struct sip_peer *peer, int force) 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) { @@ -22336,6 +24097,7 @@ static struct ast_channel *sip_request_call(const char *type, format_t format, c 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 }; @@ -22369,6 +24131,9 @@ static struct ast_channel *sip_request_call(const char *type, format_t format, c 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"); @@ -23059,6 +24824,11 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str 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); @@ -23442,9 +25212,16 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str 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) { @@ -24961,6 +26738,15 @@ static int sip_sipredirect(struct sip_pvt *p, const char *dest) 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) { @@ -25276,6 +27062,7 @@ static int load_module(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; @@ -25323,6 +27110,24 @@ static int load_module(void) 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(); @@ -25433,6 +27238,7 @@ static int unload_module(void) clear_realm_authentication(authl); + destroy_escs(); if (default_tls_cfg.certfile) ast_free(default_tls_cfg.certfile); @@ -25464,6 +27270,8 @@ static int unload_module(void) 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(); diff --git a/channels/sig_analog.c b/channels/sig_analog.c index 8e4aa3391..f8b1ede45 100644 --- a/channels/sig_analog.c +++ b/channels/sig_analog.c @@ -169,6 +169,14 @@ static int analog_get_callerid(struct analog_pvt *p, char *name, char *number, e return -1; } +static const char *analog_get_orig_dialstring(struct analog_pvt *p) +{ + if (p->calls->get_orig_dialstring) { + return p->calls->get_orig_dialstring(p->chan_pvt); + } + return ""; +} + static int analog_get_event(struct analog_pvt *p) { if (p->calls->get_event) { @@ -934,6 +942,24 @@ int analog_call(struct analog_pvt *p, struct ast_channel *ast, char *rdest, int ast_setstate(ast, AST_STATE_RINGING); index = analog_get_index(ast, p, 0); if (index > -1) { + struct ast_cc_config_params *cc_params; + + /* This is where the initial ringing frame is queued for an analog call. + * As such, this is a great time to offer CCNR to the caller if it's available. + */ + cc_params = ast_channel_get_cc_config_params(p->subs[index].owner); + if (cc_params) { + switch (ast_get_cc_monitor_policy(cc_params)) { + case AST_CC_MONITOR_NEVER: + break; + case AST_CC_MONITOR_NATIVE: + case AST_CC_MONITOR_ALWAYS: + case AST_CC_MONITOR_GENERIC: + ast_queue_cc_frame(p->subs[index].owner, AST_CC_GENERIC_MONITOR_TYPE, + analog_get_orig_dialstring(p), AST_CC_CCNR, NULL); + break; + } + } ast_queue_control(p->subs[index].owner, AST_CONTROL_RINGING); } break; diff --git a/channels/sig_analog.h b/channels/sig_analog.h index 33b642289..57fc5c1f2 100644 --- a/channels/sig_analog.h +++ b/channels/sig_analog.h @@ -213,6 +213,8 @@ struct analog_callback { void (* const cancel_cidspill)(void *pvt); int (* const confmute)(void *pvt, int mute); void (* const set_pulsedial)(void *pvt, int flag); + + const char *(* const get_orig_dialstring)(void *pvt); }; diff --git a/channels/sig_pri.c b/channels/sig_pri.c index fd3b4111e..5749e89b3 100644 --- a/channels/sig_pri.c +++ b/channels/sig_pri.c @@ -55,11 +55,32 @@ /* define this to send PRI user-user information elements */ #undef SUPPORT_USERUSER -#if 0 -#define DEFAULT_PRI_DEBUG (PRI_DEBUG_Q931_DUMP | PRI_DEBUG_Q921_DUMP | PRI_DEBUG_Q921_RAW | PRI_DEBUG_Q921_STATE) -#else -#define DEFAULT_PRI_DEBUG 0 -#endif +#if defined(HAVE_PRI_CCSS) +struct sig_pri_cc_agent_prv { + /*! Asterisk span D channel control structure. */ + struct sig_pri_pri *pri; + /*! CC id value to use with libpri. -1 if invalid. */ + long cc_id; + /*! TRUE if CC has been requested and we are waiting for the response. */ + unsigned char cc_request_response_pending; +}; + +struct sig_pri_cc_monitor_instance { + /*! \brief Asterisk span D channel control structure. */ + struct sig_pri_pri *pri; + /*! CC id value to use with libpri. (-1 if already canceled). */ + long cc_id; + /*! CC core id value. */ + int core_id; + /*! Device name(Channel name less sequence number) */ + char name[1]; +}; + +/*! Upper level agent/monitor type name. */ +static const char *sig_pri_cc_type_name; +/*! Container of sig_pri monitor instances. */ +static struct ao2_container *sig_pri_cc_monitors; +#endif /* defined(HAVE_PRI_CCSS) */ static int pri_matchdigittimeout = 3000; @@ -120,6 +141,45 @@ static void sig_pri_set_digital(struct sig_pri_chan *p, int flag) p->calls->set_digital(p->chan_pvt, flag); } +static const char *sig_pri_get_orig_dialstring(struct sig_pri_chan *p) +{ + if (p->calls->get_orig_dialstring) { + return p->calls->get_orig_dialstring(p->chan_pvt); + } + ast_log(LOG_ERROR, "get_orig_dialstring callback not defined\n"); + return ""; +} + +#if defined(HAVE_PRI_CCSS) +static void sig_pri_make_cc_dialstring(struct sig_pri_chan *p, char *buf, size_t buf_size) +{ + if (p->calls->make_cc_dialstring) { + p->calls->make_cc_dialstring(p->chan_pvt, buf, buf_size); + } else { + ast_log(LOG_ERROR, "make_cc_dialstring callback not defined\n"); + buf[0] = '\0'; + } +} +#endif /* defined(HAVE_PRI_CCSS) */ + +/*! + * \internal + * \brief Reevaluate the PRI span device state. + * \since 1.8 + * + * \param pri Asterisk D channel control structure. + * + * \return Nothing + * + * \note Assumes the pri->lock is already obtained. + */ +static void sig_pri_span_devstate_changed(struct sig_pri_pri *pri) +{ + if (pri->calls->update_span_devstate) { + pri->calls->update_span_devstate(pri); + } +} + /*! * \internal * \brief Set the caller id information in the parent module. @@ -733,6 +793,12 @@ static struct ast_channel *sig_pri_new_ast_channel(struct sig_pri_chan *p, int s pbx_builtin_setvar_helper(c, "TRANSFERCAPABILITY", ast_transfercapability2str(transfercapability)); sig_pri_set_digital(p, 1); } + if (p->pri && !pri_grab(p, p->pri)) { + sig_pri_span_devstate_changed(p->pri); + pri_rel(p->pri); + } else { + ast_log(LOG_WARNING, "Failed to grab PRI!\n"); + } return c; } @@ -1476,210 +1542,876 @@ static void sig_pri_lock_owner(struct sig_pri_pri *pri, int chanpos) } } +#if defined(HAVE_PRI_CCSS) /*! * \internal - * \brief Handle the call associated PRI subcommand events. + * \brief Compare the CC agent private data by libpri cc_id. * \since 1.8 * - * \param pri sig_pri PRI control structure. - * \param chanpos Channel position in the span. - * \param event_id PRI event id - * \param channel PRI encoded span/channel - * \param subcmds Subcommands to process if any. (Could be NULL). - * \param call_rsp libpri opaque call structure to send any responses toward. - * Could be NULL either because it is not available or the call is for the - * dummy call reference. However, this should not be NULL in the cases that - * need to use the pointer to send a response message back. - * - * \note Assumes the pri->lock is already obtained. - * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained. + * \param obj pointer to the (user-defined part) of an object. + * \param arg callback argument from ao2_callback() + * \param flags flags from ao2_callback() * - * \return Nothing + * \return values are a combination of enum _cb_results. */ -static void sig_pri_handle_subcmds(struct sig_pri_pri *pri, int chanpos, int event_id, - int channel, const struct pri_subcommands *subcmds, q931_call *call_rsp) +static int sig_pri_cc_agent_cmp_cc_id(void *obj, void *arg, int flags) { - int index; - struct ast_channel *owner; - struct ast_party_redirecting ast_redirecting; - - if (!subcmds) { - return; - } - for (index = 0; index < subcmds->counter_subcmd; ++index) { - const struct pri_subcommand *subcmd = &subcmds->subcmd[index]; - - switch (subcmd->cmd) { - case PRI_SUBCMD_CONNECTED_LINE: - sig_pri_lock_owner(pri, chanpos); - owner = pri->pvts[chanpos]->owner; - if (owner) { - struct ast_party_connected_line ast_connected; - int caller_id_update; + struct ast_cc_agent *agent_1 = obj; + struct sig_pri_cc_agent_prv *agent_prv_1 = agent_1->private_data; + struct sig_pri_cc_agent_prv *agent_prv_2 = arg; - /* Extract the connected line information */ - ast_party_connected_line_init(&ast_connected); - sig_pri_party_id_convert(&ast_connected.id, &subcmd->u.connected_line.id, - pri); + return (agent_prv_1 && agent_prv_1->pri == agent_prv_2->pri + && agent_prv_1->cc_id == agent_prv_2->cc_id) ? CMP_MATCH | CMP_STOP : 0; +} +#endif /* defined(HAVE_PRI_CCSS) */ - caller_id_update = 0; - if (ast_connected.id.name) { - /* Save name for Caller-ID update */ - ast_copy_string(pri->pvts[chanpos]->cid_name, - ast_connected.id.name, sizeof(pri->pvts[chanpos]->cid_name)); - caller_id_update = 1; - } - if (ast_connected.id.number) { - /* Save number for Caller-ID update */ - ast_copy_string(pri->pvts[chanpos]->cid_num, ast_connected.id.number, - sizeof(pri->pvts[chanpos]->cid_num)); - pri->pvts[chanpos]->cid_ton = ast_connected.id.number_type; - caller_id_update = 1; - } else { - ast_connected.id.number = ast_strdup(""); - } - ast_connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER; +#if defined(HAVE_PRI_CCSS) +/*! + * \internal + * \brief Find the CC agent by libpri cc_id. + * \since 1.8 + * + * \param pri sig_pri PRI control structure. + * \param cc_id CC record ID to find. + * + * \note + * Since agents are refcounted, and this function returns + * a reference to the agent, it is imperative that you decrement + * the refcount of the agent once you have finished using it. + * + * \retval agent on success. + * \retval NULL not found. + */ +static struct ast_cc_agent *sig_pri_find_cc_agent_by_cc_id(struct sig_pri_pri *pri, long cc_id) +{ + struct sig_pri_cc_agent_prv finder = { + .pri = pri, + .cc_id = cc_id, + }; - pri->pvts[chanpos]->cid_subaddr[0] = '\0'; -#if defined(HAVE_PRI_SUBADDR) - if (ast_connected.id.subaddress.valid) { - ast_party_subaddress_set(&owner->cid.subaddress, - &ast_connected.id.subaddress); - if (ast_connected.id.subaddress.str) { - ast_copy_string(pri->pvts[chanpos]->cid_subaddr, - ast_connected.id.subaddress.str, - sizeof(pri->pvts[chanpos]->cid_subaddr)); - } - } -#endif /* defined(HAVE_PRI_SUBADDR) */ - if (caller_id_update) { - pri->pvts[chanpos]->callingpres = - ast_connected.id.number_presentation; - sig_pri_set_caller_id(pri->pvts[chanpos]); - ast_set_callerid(owner, S_OR(ast_connected.id.number, NULL), - S_OR(ast_connected.id.name, NULL), - S_OR(ast_connected.id.number, NULL)); - } + return ast_cc_agent_callback(0, sig_pri_cc_agent_cmp_cc_id, &finder, + sig_pri_cc_type_name); +} +#endif /* defined(HAVE_PRI_CCSS) */ - /* Update the connected line information on the other channel */ - if (event_id != PRI_EVENT_RING) { - /* This connected_line update was not from a SETUP message. */ - ast_channel_queue_connected_line_update(owner, &ast_connected); - } +#if defined(HAVE_PRI_CCSS) +/*! + * \internal + * \brief Compare the CC monitor instance by libpri cc_id. + * \since 1.8 + * + * \param obj pointer to the (user-defined part) of an object. + * \param arg callback argument from ao2_callback() + * \param flags flags from ao2_callback() + * + * \return values are a combination of enum _cb_results. + */ +static int sig_pri_cc_monitor_cmp_cc_id(void *obj, void *arg, int flags) +{ + struct sig_pri_cc_monitor_instance *monitor_1 = obj; + struct sig_pri_cc_monitor_instance *monitor_2 = arg; - ast_party_connected_line_free(&ast_connected); - ast_channel_unlock(owner); - } - break; - case PRI_SUBCMD_REDIRECTING: - sig_pri_lock_owner(pri, chanpos); - owner = pri->pvts[chanpos]->owner; - if (owner) { - sig_pri_redirecting_convert(&ast_redirecting, &subcmd->u.redirecting, - &owner->redirecting, pri); + return (monitor_1->pri == monitor_2->pri + && monitor_1->cc_id == monitor_2->cc_id) ? CMP_MATCH | CMP_STOP : 0; +} +#endif /* defined(HAVE_PRI_CCSS) */ -/*! \todo XXX Original called data can be put in a channel data store that is inherited. */ +#if defined(HAVE_PRI_CCSS) +/*! + * \internal + * \brief Find the CC monitor instance by libpri cc_id. + * \since 1.8 + * + * \param pri sig_pri PRI control structure. + * \param cc_id CC record ID to find. + * + * \note + * Since monitor_instances are refcounted, and this function returns + * a reference to the instance, it is imperative that you decrement + * the refcount of the instance once you have finished using it. + * + * \retval monitor_instance on success. + * \retval NULL not found. + */ +static struct sig_pri_cc_monitor_instance *sig_pri_find_cc_monitor_by_cc_id(struct sig_pri_pri *pri, long cc_id) +{ + struct sig_pri_cc_monitor_instance finder = { + .pri = pri, + .cc_id = cc_id, + }; - ast_channel_set_redirecting(owner, &ast_redirecting); - if (event_id != PRI_EVENT_RING) { - /* This redirection was not from a SETUP message. */ - ast_channel_queue_redirecting_update(owner, &ast_redirecting); - } - ast_party_redirecting_free(&ast_redirecting); + return ao2_callback(sig_pri_cc_monitors, 0, sig_pri_cc_monitor_cmp_cc_id, &finder); +} +#endif /* defined(HAVE_PRI_CCSS) */ - ast_channel_unlock(owner); - } - break; -#if defined(HAVE_PRI_CALL_REROUTING) - case PRI_SUBCMD_REROUTING: - sig_pri_lock_owner(pri, chanpos); - owner = pri->pvts[chanpos]->owner; - if (owner) { - struct pri_party_redirecting pri_deflection; +#if defined(HAVE_PRI_CCSS) +/*! + * \internal + * \brief Destroy the given monitor instance. + * \since 1.8 + * + * \param data Monitor instance to destroy. + * + * \return Nothing + */ +static void sig_pri_cc_monitor_instance_destroy(void *data) +{ + struct sig_pri_cc_monitor_instance *monitor_instance = data; - if (!call_rsp) { - ast_channel_unlock(owner); - ast_log(LOG_WARNING, - "CallRerouting/CallDeflection to '%s' without call!\n", - subcmd->u.rerouting.deflection.to.number.str); - break; - } + if (monitor_instance->cc_id != -1) { + ast_mutex_lock(&monitor_instance->pri->lock); + pri_cc_cancel(monitor_instance->pri->pri, monitor_instance->cc_id); + ast_mutex_unlock(&monitor_instance->pri->lock); + } + monitor_instance->pri->calls->module_unref(); +} +#endif /* defined(HAVE_PRI_CCSS) */ - pri_deflection = subcmd->u.rerouting.deflection; +#if defined(HAVE_PRI_CCSS) +/*! + * \internal + * \brief Construct a new monitor instance. + * \since 1.8 + * + * \param core_id CC core ID. + * \param pri sig_pri PRI control structure. + * \param cc_id CC record ID. + * \param device_name Name of device (Asterisk channel name less sequence number). + * + * \note + * Since monitor_instances are refcounted, and this function returns + * a reference to the instance, it is imperative that you decrement + * the refcount of the instance once you have finished using it. + * + * \retval monitor_instance on success. + * \retval NULL on error. + */ +static struct sig_pri_cc_monitor_instance *sig_pri_cc_monitor_instance_init(int core_id, struct sig_pri_pri *pri, long cc_id, const char *device_name) +{ + struct sig_pri_cc_monitor_instance *monitor_instance; - ast_string_field_set(owner, call_forward, pri_deflection.to.number.str); + if (!pri->calls->module_ref || !pri->calls->module_unref) { + return NULL; + } - /* Adjust the deflecting to number based upon the subscription option. */ - switch (subcmd->u.rerouting.subscription_option) { - case 0: /* noNotification */ - case 1: /* notificationWithoutDivertedToNr */ - /* Delete the number because the far end is not supposed to see it. */ - pri_deflection.to.number.presentation = - PRI_PRES_RESTRICTED | PRI_PRES_USER_NUMBER_UNSCREENED; - pri_deflection.to.number.plan = - (PRI_TON_UNKNOWN << 4) | PRI_NPI_E163_E164; - pri_deflection.to.number.str[0] = '\0'; - break; - case 2: /* notificationWithDivertedToNr */ - break; - case 3: /* notApplicable */ - default: - break; - } - sig_pri_redirecting_convert(&ast_redirecting, &pri_deflection, - &owner->redirecting, pri); - ast_channel_set_redirecting(owner, &ast_redirecting); - ast_party_redirecting_free(&ast_redirecting); + monitor_instance = ao2_alloc(sizeof(*monitor_instance) + strlen(device_name), + sig_pri_cc_monitor_instance_destroy); + if (!monitor_instance) { + return NULL; + } - /* - * Send back positive ACK to CallRerouting/CallDeflection. - * - * Note: This call will be hungup by the dial application when - * it processes the call_forward string set above. - */ - pri_rerouting_rsp(pri->pri, call_rsp, subcmd->u.rerouting.invoke_id, - PRI_REROUTING_RSP_OK_CLEAR); + monitor_instance->cc_id = cc_id; + monitor_instance->pri = pri; + monitor_instance->core_id = core_id; + strcpy(monitor_instance->name, device_name); - /* This line is BUSY to further attempts by this dialing attempt. */ - ast_queue_control(owner, AST_CONTROL_BUSY); + pri->calls->module_ref(); - ast_channel_unlock(owner); - } - break; -#endif /* defined(HAVE_PRI_CALL_REROUTING) */ - default: - ast_debug(2, - "Unknown call subcommand(%d) in %s event on channel %d/%d on span %d.\n", - subcmd->cmd, pri_event2str(event_id), PRI_SPAN(channel), - PRI_CHANNEL(channel), pri->span); - break; - } - } + ao2_link(sig_pri_cc_monitors, monitor_instance); + return monitor_instance; } +#endif /* defined(HAVE_PRI_CCSS) */ -#if defined(HAVE_PRI_CALL_HOLD) +#if defined(HAVE_PRI_CCSS) /*! * \internal - * \brief Attempt to transfer the active call to the held call. + * \brief Announce to the CC core that protocol CC monitor is available for this call. * \since 1.8 * * \param pri sig_pri PRI control structure. - * \param active_call Active call to transfer. - * \param held_call Held call to transfer. + * \param chanpos Channel position in the span. + * \param cc_id CC record ID. + * \param service CCBS/CCNR indication. * * \note Assumes the pri->lock is already obtained. + * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained. + * \note Assumes the sig_pri_lock_owner(pri, chanpos) is already obtained. * * \retval 0 on success. * \retval -1 on error. */ -static int sig_pri_attempt_transfer(struct sig_pri_pri *pri, q931_call *active_call, q931_call *held_call) +static int sig_pri_cc_available(struct sig_pri_pri *pri, int chanpos, long cc_id, enum ast_cc_service_type service) { - int retval; - int active_chanpos; - int held_chanpos; - struct ast_channel *active_ast; - struct ast_channel *held_ast; - struct ast_channel *bridged; + struct sig_pri_chan *pvt; + struct ast_cc_config_params *cc_params; + struct sig_pri_cc_monitor_instance *monitor; + enum ast_cc_monitor_policies monitor_policy; + int core_id; + int res; + char device_name[AST_CHANNEL_NAME]; + char dialstring[AST_CHANNEL_NAME]; + + pvt = pri->pvts[chanpos]; + + core_id = ast_cc_get_current_core_id(pvt->owner); + if (core_id == -1) { + return -1; + } + + cc_params = ast_channel_get_cc_config_params(pvt->owner); + if (!cc_params) { + return -1; + } + + res = -1; + monitor_policy = ast_get_cc_monitor_policy(cc_params); + switch (monitor_policy) { + case AST_CC_MONITOR_NEVER: + /* CCSS is not enabled. */ + break; + case AST_CC_MONITOR_NATIVE: + case AST_CC_MONITOR_ALWAYS: + /* + * If it is AST_CC_MONITOR_ALWAYS and native fails we will attempt the fallback + * later in the call to sig_pri_cc_generic_check(). + */ + ast_channel_get_device_name(pvt->owner, device_name, sizeof(device_name)); + sig_pri_make_cc_dialstring(pvt, dialstring, sizeof(dialstring)); + monitor = sig_pri_cc_monitor_instance_init(core_id, pri, cc_id, device_name); + if (!monitor) { + break; + } + res = ast_queue_cc_frame(pvt->owner, sig_pri_cc_type_name, dialstring, service, + monitor); + if (res) { + monitor->cc_id = -1; + ao2_unlink(sig_pri_cc_monitors, monitor); + ao2_ref(monitor, -1); + } + break; + case AST_CC_MONITOR_GENERIC: + ast_queue_cc_frame(pvt->owner, AST_CC_GENERIC_MONITOR_TYPE, + sig_pri_get_orig_dialstring(pvt), service, NULL); + /* Say it failed to force caller to cancel native CC. */ + break; + } + return res; +} +#endif /* defined(HAVE_PRI_CCSS) */ + +/*! + * \internal + * \brief Check if generic CC monitor is needed and request it. + * \since 1.8 + * + * \param pri sig_pri PRI control structure. + * \param chanpos Channel position in the span. + * \param service CCBS/CCNR indication. + * + * \note Assumes the pri->lock is already obtained. + * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained. + * + * \return Nothing + */ +static void sig_pri_cc_generic_check(struct sig_pri_pri *pri, int chanpos, enum ast_cc_service_type service) +{ + struct ast_channel *owner; + struct ast_cc_config_params *cc_params; +#if defined(HAVE_PRI_CCSS) + struct ast_cc_monitor *monitor; + char device_name[AST_CHANNEL_NAME]; +#endif /* defined(HAVE_PRI_CCSS) */ + enum ast_cc_monitor_policies monitor_policy; + int core_id; + + if (!pri->pvts[chanpos]->outgoing) { + /* This is not an outgoing call so it cannot be CC monitor. */ + return; + } + + sig_pri_lock_owner(pri, chanpos); + owner = pri->pvts[chanpos]->owner; + if (!owner) { + return; + } + core_id = ast_cc_get_current_core_id(owner); + if (core_id == -1) { + /* No CC core setup */ + goto done; + } + + cc_params = ast_channel_get_cc_config_params(owner); + if (!cc_params) { + /* Could not get CC config parameters. */ + goto done; + } + +#if defined(HAVE_PRI_CCSS) + ast_channel_get_device_name(owner, device_name, sizeof(device_name)); + monitor = ast_cc_get_monitor_by_recall_core_id(core_id, device_name); + if (monitor) { + /* CC monitor is already present so no need for generic CC. */ + ao2_ref(monitor, -1); + goto done; + } +#endif /* defined(HAVE_PRI_CCSS) */ + + monitor_policy = ast_get_cc_monitor_policy(cc_params); + switch (monitor_policy) { + case AST_CC_MONITOR_NEVER: + /* CCSS is not enabled. */ + break; + case AST_CC_MONITOR_NATIVE: + if (pri->sig == SIG_BRI_PTMP && pri->nodetype == PRI_NETWORK) { + /* Request generic CC monitor. */ + ast_queue_cc_frame(owner, AST_CC_GENERIC_MONITOR_TYPE, + sig_pri_get_orig_dialstring(pri->pvts[chanpos]), service, NULL); + } + break; + case AST_CC_MONITOR_ALWAYS: + if (pri->sig == SIG_BRI_PTMP && pri->nodetype != PRI_NETWORK) { + /* + * Cannot monitor PTMP TE side since this is not defined. + * We are playing the roll of a phone in this case and + * a phone cannot monitor a party over the network without + * protocol help. + */ + break; + } + /* + * We are either falling back or this is a PTMP NT span. + * Request generic CC monitor. + */ + ast_queue_cc_frame(owner, AST_CC_GENERIC_MONITOR_TYPE, + sig_pri_get_orig_dialstring(pri->pvts[chanpos]), service, NULL); + break; + case AST_CC_MONITOR_GENERIC: + if (pri->sig == SIG_BRI_PTMP && pri->nodetype == PRI_NETWORK) { + /* Request generic CC monitor. */ + ast_queue_cc_frame(owner, AST_CC_GENERIC_MONITOR_TYPE, + sig_pri_get_orig_dialstring(pri->pvts[chanpos]), service, NULL); + } + break; + } + +done: + ast_channel_unlock(owner); +} + +#if defined(HAVE_PRI_CCSS) +/*! + * \internal + * \brief The CC link canceled the CC instance. + * \since 1.8 + * + * \param pri sig_pri PRI control structure. + * \param cc_id CC record ID. + * \param is_agent TRUE if the cc_id is for an agent. + * + * \return Nothing + */ +static void sig_pri_cc_link_canceled(struct sig_pri_pri *pri, long cc_id, int is_agent) +{ + if (is_agent) { + struct ast_cc_agent *agent; + + agent = sig_pri_find_cc_agent_by_cc_id(pri, cc_id); + if (!agent) { + return; + } + ast_cc_failed(agent->core_id, "%s agent got canceled by link", + sig_pri_cc_type_name); + ao2_ref(agent, -1); + } else { + struct sig_pri_cc_monitor_instance *monitor; + + monitor = sig_pri_find_cc_monitor_by_cc_id(pri, cc_id); + if (!monitor) { + return; + } + monitor->cc_id = -1; + ast_cc_monitor_failed(monitor->core_id, monitor->name, + "%s monitor got canceled by link", sig_pri_cc_type_name); + ao2_ref(monitor, -1); + } +} +#endif /* defined(HAVE_PRI_CCSS) */ + +/*! + * \internal + * \brief TRUE if PRI event came in on a CIS call. + * \since 1.8 + * + * \param channel PRI encoded span/channel + * + * \retval non-zero if CIS call. + */ +static int sig_pri_is_cis_call(int channel) +{ + return channel != -1 && (channel & PRI_CIS_CALL); +} + +/*! + * \internal + * \brief Handle the CIS associated PRI subcommand events. + * \since 1.8 + * + * \param pri sig_pri PRI control structure. + * \param event_id PRI event id + * \param subcmds Subcommands to process if any. (Could be NULL). + * \param call_rsp libpri opaque call structure to send any responses toward. + * Could be NULL either because it is not available or the call is for the + * dummy call reference. However, this should not be NULL in the cases that + * need to use the pointer to send a response message back. + * + * \note Assumes the pri->lock is already obtained. + * + * \return Nothing + */ +static void sig_pri_handle_cis_subcmds(struct sig_pri_pri *pri, int event_id, + const struct pri_subcommands *subcmds, q931_call *call_rsp) +{ + int index; +#if defined(HAVE_PRI_CCSS) + struct ast_cc_agent *agent; + struct sig_pri_cc_agent_prv *agent_prv; + struct sig_pri_cc_monitor_instance *monitor; +#endif /* defined(HAVE_PRI_CCSS) */ + + if (!subcmds) { + return; + } + for (index = 0; index < subcmds->counter_subcmd; ++index) { + const struct pri_subcommand *subcmd = &subcmds->subcmd[index]; + + switch (subcmd->cmd) { +#if defined(STATUS_REQUEST_PLACE_HOLDER) + case PRI_SUBCMD_STATUS_REQ: + case PRI_SUBCMD_STATUS_REQ_RSP: + /* Ignore for now. */ + break; +#endif /* defined(STATUS_REQUEST_PLACE_HOLDER) */ +#if defined(HAVE_PRI_CCSS) + case PRI_SUBCMD_CC_REQ: + agent = sig_pri_find_cc_agent_by_cc_id(pri, subcmd->u.cc_request.cc_id); + if (!agent) { + pri_cc_cancel(pri->pri, subcmd->u.cc_request.cc_id); + break; + } + if (!ast_cc_request_is_within_limits()) { + if (pri_cc_req_rsp(pri->pri, subcmd->u.cc_request.cc_id, + 5/* queue_full */)) { + pri_cc_cancel(pri->pri, subcmd->u.cc_request.cc_id); + } + ast_cc_failed(agent->core_id, "%s agent system CC queue full", + sig_pri_cc_type_name); + ao2_ref(agent, -1); + break; + } + agent_prv = agent->private_data; + agent_prv->cc_request_response_pending = 1; + if (ast_cc_agent_accept_request(agent->core_id, + "%s caller accepted CC offer.", sig_pri_cc_type_name)) { + agent_prv->cc_request_response_pending = 0; + if (pri_cc_req_rsp(pri->pri, subcmd->u.cc_request.cc_id, + 2/* short_term_denial */)) { + pri_cc_cancel(pri->pri, subcmd->u.cc_request.cc_id); + } + ast_cc_failed(agent->core_id, "%s agent CC core request accept failed", + sig_pri_cc_type_name); + } + ao2_ref(agent, -1); + break; +#endif /* defined(HAVE_PRI_CCSS) */ +#if defined(HAVE_PRI_CCSS) + case PRI_SUBCMD_CC_REQ_RSP: + monitor = sig_pri_find_cc_monitor_by_cc_id(pri, + subcmd->u.cc_request_rsp.cc_id); + if (!monitor) { + pri_cc_cancel(pri->pri, subcmd->u.cc_request_rsp.cc_id); + break; + } + switch (subcmd->u.cc_request_rsp.status) { + case 0:/* success */ + ast_cc_monitor_request_acked(monitor->core_id, + "%s far end accepted CC request", sig_pri_cc_type_name); + break; + case 1:/* timeout */ + ast_verb(2, "core_id:%d %s CC request timeout\n", monitor->core_id, + sig_pri_cc_type_name); + ast_cc_monitor_failed(monitor->core_id, monitor->name, + "%s CC request timeout", sig_pri_cc_type_name); + break; + case 2:/* error */ + ast_verb(2, "core_id:%d %s CC request error: %s\n", monitor->core_id, + sig_pri_cc_type_name, + pri_facility_error2str(subcmd->u.cc_request_rsp.fail_code)); + ast_cc_monitor_failed(monitor->core_id, monitor->name, + "%s CC request error", sig_pri_cc_type_name); + break; + case 3:/* reject */ + ast_verb(2, "core_id:%d %s CC request reject: %s\n", monitor->core_id, + sig_pri_cc_type_name, + pri_facility_reject2str(subcmd->u.cc_request_rsp.fail_code)); + ast_cc_monitor_failed(monitor->core_id, monitor->name, + "%s CC request reject", sig_pri_cc_type_name); + break; + default: + ast_verb(2, "core_id:%d %s CC request unknown status %d\n", + monitor->core_id, sig_pri_cc_type_name, + subcmd->u.cc_request_rsp.status); + ast_cc_monitor_failed(monitor->core_id, monitor->name, + "%s CC request unknown status", sig_pri_cc_type_name); + break; + } + ao2_ref(monitor, -1); + break; +#endif /* defined(HAVE_PRI_CCSS) */ +#if defined(HAVE_PRI_CCSS) + case PRI_SUBCMD_CC_REMOTE_USER_FREE: + monitor = sig_pri_find_cc_monitor_by_cc_id(pri, + subcmd->u.cc_remote_user_free.cc_id); + if (!monitor) { + pri_cc_cancel(pri->pri, subcmd->u.cc_remote_user_free.cc_id); + break; + } + ast_cc_monitor_callee_available(monitor->core_id, + "%s callee has become available", sig_pri_cc_type_name); + ao2_ref(monitor, -1); + break; +#endif /* defined(HAVE_PRI_CCSS) */ +#if defined(HAVE_PRI_CCSS) + case PRI_SUBCMD_CC_B_FREE: + monitor = sig_pri_find_cc_monitor_by_cc_id(pri, + subcmd->u.cc_b_free.cc_id); + if (!monitor) { + pri_cc_cancel(pri->pri, subcmd->u.cc_b_free.cc_id); + break; + } + ast_cc_monitor_party_b_free(monitor->core_id); + ao2_ref(monitor, -1); + break; +#endif /* defined(HAVE_PRI_CCSS) */ +#if defined(HAVE_PRI_CCSS) + case PRI_SUBCMD_CC_STATUS_REQ: + monitor = sig_pri_find_cc_monitor_by_cc_id(pri, + subcmd->u.cc_status_req.cc_id); + if (!monitor) { + pri_cc_cancel(pri->pri, subcmd->u.cc_status_req.cc_id); + break; + } + ast_cc_monitor_status_request(monitor->core_id); + ao2_ref(monitor, -1); + break; +#endif /* defined(HAVE_PRI_CCSS) */ +#if defined(HAVE_PRI_CCSS) + case PRI_SUBCMD_CC_STATUS_REQ_RSP: + agent = sig_pri_find_cc_agent_by_cc_id(pri, subcmd->u.cc_status_req_rsp.cc_id); + if (!agent) { + pri_cc_cancel(pri->pri, subcmd->u.cc_status_req_rsp.cc_id); + break; + } + ast_cc_agent_status_response(agent->core_id, + subcmd->u.cc_status_req_rsp.status ? AST_DEVICE_INUSE + : AST_DEVICE_NOT_INUSE); + ao2_ref(agent, -1); + break; +#endif /* defined(HAVE_PRI_CCSS) */ +#if defined(HAVE_PRI_CCSS) + case PRI_SUBCMD_CC_STATUS: + agent = sig_pri_find_cc_agent_by_cc_id(pri, subcmd->u.cc_status.cc_id); + if (!agent) { + pri_cc_cancel(pri->pri, subcmd->u.cc_status.cc_id); + break; + } + if (subcmd->u.cc_status.status) { + ast_cc_agent_caller_busy(agent->core_id, "%s agent caller is busy", + sig_pri_cc_type_name); + } else { + ast_cc_agent_caller_available(agent->core_id, + "%s agent caller is available", sig_pri_cc_type_name); + } + ao2_ref(agent, -1); + break; +#endif /* defined(HAVE_PRI_CCSS) */ +#if defined(HAVE_PRI_CCSS) + case PRI_SUBCMD_CC_CANCEL: + sig_pri_cc_link_canceled(pri, subcmd->u.cc_cancel.cc_id, + subcmd->u.cc_cancel.is_agent); + break; +#endif /* defined(HAVE_PRI_CCSS) */ +#if defined(HAVE_PRI_CCSS) + case PRI_SUBCMD_CC_STOP_ALERTING: + monitor = sig_pri_find_cc_monitor_by_cc_id(pri, + subcmd->u.cc_stop_alerting.cc_id); + if (!monitor) { + pri_cc_cancel(pri->pri, subcmd->u.cc_stop_alerting.cc_id); + break; + } + ast_cc_monitor_stop_ringing(monitor->core_id); + ao2_ref(monitor, -1); + break; +#endif /* defined(HAVE_PRI_CCSS) */ + default: + ast_debug(2, + "Unknown CIS subcommand(%d) in %s event on span %d.\n", + subcmd->cmd, pri_event2str(event_id), pri->span); + break; + } + } +} + +/*! + * \internal + * \brief Handle the call associated PRI subcommand events. + * \since 1.8 + * + * \param pri sig_pri PRI control structure. + * \param chanpos Channel position in the span. + * \param event_id PRI event id + * \param channel PRI encoded span/channel + * \param subcmds Subcommands to process if any. (Could be NULL). + * \param call_rsp libpri opaque call structure to send any responses toward. + * Could be NULL either because it is not available or the call is for the + * dummy call reference. However, this should not be NULL in the cases that + * need to use the pointer to send a response message back. + * + * \note Assumes the pri->lock is already obtained. + * \note Assumes the sig_pri_lock_private(pri->pvts[chanpos]) is already obtained. + * + * \return Nothing + */ +static void sig_pri_handle_subcmds(struct sig_pri_pri *pri, int chanpos, int event_id, + int channel, const struct pri_subcommands *subcmds, q931_call *call_rsp) +{ + int index; + struct ast_channel *owner; + struct ast_party_redirecting ast_redirecting; + + if (!subcmds) { + return; + } + for (index = 0; index < subcmds->counter_subcmd; ++index) { + const struct pri_subcommand *subcmd = &subcmds->subcmd[index]; + + switch (subcmd->cmd) { + case PRI_SUBCMD_CONNECTED_LINE: + sig_pri_lock_owner(pri, chanpos); + owner = pri->pvts[chanpos]->owner; + if (owner) { + struct ast_party_connected_line ast_connected; + int caller_id_update; + + /* Extract the connected line information */ + ast_party_connected_line_init(&ast_connected); + sig_pri_party_id_convert(&ast_connected.id, &subcmd->u.connected_line.id, + pri); + + caller_id_update = 0; + if (ast_connected.id.name) { + /* Save name for Caller-ID update */ + ast_copy_string(pri->pvts[chanpos]->cid_name, + ast_connected.id.name, sizeof(pri->pvts[chanpos]->cid_name)); + caller_id_update = 1; + } + if (ast_connected.id.number) { + /* Save number for Caller-ID update */ + ast_copy_string(pri->pvts[chanpos]->cid_num, ast_connected.id.number, + sizeof(pri->pvts[chanpos]->cid_num)); + pri->pvts[chanpos]->cid_ton = ast_connected.id.number_type; + caller_id_update = 1; + } else { + ast_connected.id.number = ast_strdup(""); + } + ast_connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER; + + pri->pvts[chanpos]->cid_subaddr[0] = '\0'; +#if defined(HAVE_PRI_SUBADDR) + if (ast_connected.id.subaddress.valid) { + ast_party_subaddress_set(&owner->cid.subaddress, + &ast_connected.id.subaddress); + if (ast_connected.id.subaddress.str) { + ast_copy_string(pri->pvts[chanpos]->cid_subaddr, + ast_connected.id.subaddress.str, + sizeof(pri->pvts[chanpos]->cid_subaddr)); + } + } +#endif /* defined(HAVE_PRI_SUBADDR) */ + if (caller_id_update) { + pri->pvts[chanpos]->callingpres = + ast_connected.id.number_presentation; + sig_pri_set_caller_id(pri->pvts[chanpos]); + ast_set_callerid(owner, S_OR(ast_connected.id.number, NULL), + S_OR(ast_connected.id.name, NULL), + S_OR(ast_connected.id.number, NULL)); + } + + /* Update the connected line information on the other channel */ + if (event_id != PRI_EVENT_RING) { + /* This connected_line update was not from a SETUP message. */ + ast_channel_queue_connected_line_update(owner, &ast_connected); + } + + ast_party_connected_line_free(&ast_connected); + ast_channel_unlock(owner); + } + break; + case PRI_SUBCMD_REDIRECTING: + sig_pri_lock_owner(pri, chanpos); + owner = pri->pvts[chanpos]->owner; + if (owner) { + sig_pri_redirecting_convert(&ast_redirecting, &subcmd->u.redirecting, + &owner->redirecting, pri); + +/*! \todo XXX Original called data can be put in a channel data store that is inherited. */ + + ast_channel_set_redirecting(owner, &ast_redirecting); + if (event_id != PRI_EVENT_RING) { + /* This redirection was not from a SETUP message. */ + ast_channel_queue_redirecting_update(owner, &ast_redirecting); + } + ast_party_redirecting_free(&ast_redirecting); + + ast_channel_unlock(owner); + } + break; +#if defined(HAVE_PRI_CALL_REROUTING) + case PRI_SUBCMD_REROUTING: + sig_pri_lock_owner(pri, chanpos); + owner = pri->pvts[chanpos]->owner; + if (owner) { + struct pri_party_redirecting pri_deflection; + + if (!call_rsp) { + ast_channel_unlock(owner); + ast_log(LOG_WARNING, + "CallRerouting/CallDeflection to '%s' without call!\n", + subcmd->u.rerouting.deflection.to.number.str); + break; + } + + pri_deflection = subcmd->u.rerouting.deflection; + + ast_string_field_set(owner, call_forward, pri_deflection.to.number.str); + + /* Adjust the deflecting to number based upon the subscription option. */ + switch (subcmd->u.rerouting.subscription_option) { + case 0: /* noNotification */ + case 1: /* notificationWithoutDivertedToNr */ + /* Delete the number because the far end is not supposed to see it. */ + pri_deflection.to.number.presentation = + PRI_PRES_RESTRICTED | PRI_PRES_USER_NUMBER_UNSCREENED; + pri_deflection.to.number.plan = + (PRI_TON_UNKNOWN << 4) | PRI_NPI_E163_E164; + pri_deflection.to.number.str[0] = '\0'; + break; + case 2: /* notificationWithDivertedToNr */ + break; + case 3: /* notApplicable */ + default: + break; + } + sig_pri_redirecting_convert(&ast_redirecting, &pri_deflection, + &owner->redirecting, pri); + ast_channel_set_redirecting(owner, &ast_redirecting); + ast_party_redirecting_free(&ast_redirecting); + + /* + * Send back positive ACK to CallRerouting/CallDeflection. + * + * Note: This call will be hungup by the dial application when + * it processes the call_forward string set above. + */ + pri_rerouting_rsp(pri->pri, call_rsp, subcmd->u.rerouting.invoke_id, + PRI_REROUTING_RSP_OK_CLEAR); + + /* This line is BUSY to further attempts by this dialing attempt. */ + ast_queue_control(owner, AST_CONTROL_BUSY); + + ast_channel_unlock(owner); + } + break; +#endif /* defined(HAVE_PRI_CALL_REROUTING) */ +#if defined(HAVE_PRI_CCSS) + case PRI_SUBCMD_CC_AVAILABLE: + sig_pri_lock_owner(pri, chanpos); + owner = pri->pvts[chanpos]->owner; + if (owner) { + enum ast_cc_service_type service; + + switch (event_id) { + case PRI_EVENT_RINGING: + service = AST_CC_CCNR; + break; + case PRI_EVENT_HANGUP_REQ: + /* We will assume that the cause was busy/congestion. */ + service = AST_CC_CCBS; + break; + default: + service = AST_CC_NONE; + break; + } + if (service == AST_CC_NONE + || sig_pri_cc_available(pri, chanpos, subcmd->u.cc_available.cc_id, + service)) { + pri_cc_cancel(pri->pri, subcmd->u.cc_available.cc_id); + } + ast_channel_unlock(owner); + } else { + /* No asterisk channel. */ + pri_cc_cancel(pri->pri, subcmd->u.cc_available.cc_id); + } + break; +#endif /* defined(HAVE_PRI_CCSS) */ +#if defined(HAVE_PRI_CCSS) + case PRI_SUBCMD_CC_CALL: + sig_pri_lock_owner(pri, chanpos); + owner = pri->pvts[chanpos]->owner; + if (owner) { + struct ast_cc_agent *agent; + + agent = sig_pri_find_cc_agent_by_cc_id(pri, subcmd->u.cc_call.cc_id); + if (agent) { + ast_setup_cc_recall_datastore(owner, agent->core_id); + ast_cc_agent_set_interfaces_chanvar(owner); + ast_cc_agent_recalling(agent->core_id, + "%s caller is attempting recall", sig_pri_cc_type_name); + ao2_ref(agent, -1); + } + + ast_channel_unlock(owner); + } + break; +#endif /* defined(HAVE_PRI_CCSS) */ +#if defined(HAVE_PRI_CCSS) + case PRI_SUBCMD_CC_CANCEL: + sig_pri_cc_link_canceled(pri, subcmd->u.cc_cancel.cc_id, + subcmd->u.cc_cancel.is_agent); + break; +#endif /* defined(HAVE_PRI_CCSS) */ + default: + ast_debug(2, + "Unknown call subcommand(%d) in %s event on channel %d/%d on span %d.\n", + subcmd->cmd, pri_event2str(event_id), PRI_SPAN(channel), + PRI_CHANNEL(channel), pri->span); + break; + } + } +} + +#if defined(HAVE_PRI_CALL_HOLD) +/*! + * \internal + * \brief Attempt to transfer the active call to the held call. + * \since 1.8 + * + * \param pri sig_pri PRI control structure. + * \param active_call Active call to transfer. + * \param held_call Held call to transfer. + * + * \note Assumes the pri->lock is already obtained. + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int sig_pri_attempt_transfer(struct sig_pri_pri *pri, q931_call *active_call, q931_call *held_call) +{ + int retval; + int active_chanpos; + int held_chanpos; + struct ast_channel *active_ast; + struct ast_channel *held_ast; + struct ast_channel *bridged; active_chanpos = pri_find_pri_call(pri, active_call); held_chanpos = pri_find_pri_call(pri, held_call); @@ -1793,6 +2525,7 @@ static int sig_pri_handle_hold(struct sig_pri_pri *pri, pri_event *ev) f.subclass.integer = AST_CONTROL_HOLD; ast_queue_frame(owner, &f); + sig_pri_span_devstate_changed(pri); retval = 0; } @@ -1866,6 +2599,7 @@ static void sig_pri_handle_retrieve(struct sig_pri_pri *pri, pri_event *ev) pri_queue_frame(pri->pvts[chanpos], &f, pri); } sig_pri_unlock_private(pri->pvts[chanpos]); + sig_pri_span_devstate_changed(pri); pri_retrieve_ack(pri->pri, ev->retrieve.call, PVT_TO_CHANNEL(pri->pvts[chanpos])); } @@ -2094,10 +2828,12 @@ static void *pri_dchannel(void *vpri) } pri->resetting = 0; /* Take the channels from inalarm condition */ - for (i = 0; i < pri->numchans; i++) + for (i = 0; i < pri->numchans; i++) { if (pri->pvts[i]) { pri->pvts[i]->inalarm = 0; } + } + sig_pri_span_devstate_changed(pri); break; case PRI_EVENT_DCHAN_DOWN: pri_find_dchan(pri); @@ -2128,6 +2864,7 @@ static void *pri_dchannel(void *vpri) p->inalarm = 1; } } + sig_pri_span_devstate_changed(pri); } break; case PRI_EVENT_RESTART: @@ -2180,6 +2917,11 @@ static void *pri_dchannel(void *vpri) } break; case PRI_EVENT_KEYPAD_DIGIT: + if (sig_pri_is_cis_call(e->digit.channel)) { + sig_pri_handle_cis_subcmds(pri, e->e, e->digit.subcmds, + e->digit.call); + break; + } chanpos = pri_find_principle(pri, e->digit.channel, e->digit.call); if (chanpos < 0) { ast_log(LOG_WARNING, "KEYPAD_DIGITs received on unconfigured channel %d/%d span %d\n", @@ -2210,6 +2952,11 @@ static void *pri_dchannel(void *vpri) break; case PRI_EVENT_INFO_RECEIVED: + if (sig_pri_is_cis_call(e->ring.channel)) { + sig_pri_handle_cis_subcmds(pri, e->e, e->ring.subcmds, + e->ring.call); + break; + } chanpos = pri_find_principle(pri, e->ring.channel, e->ring.call); if (chanpos < 0) { ast_log(LOG_WARNING, "INFO received on unconfigured channel %d/%d span %d\n", @@ -2262,6 +3009,8 @@ static void *pri_dchannel(void *vpri) snprintf(db_answer, sizeof(db_answer), "%s:%u", SRVST_TYPE_OOS, *why); ast_db_put(db_chan_name, SRVST_DBKEY, db_answer); + } else { + sig_pri_span_devstate_changed(pri); } break; case 2: /* out-of-service */ @@ -2271,6 +3020,7 @@ static void *pri_dchannel(void *vpri) snprintf(db_answer, sizeof(db_answer), "%s:%u", SRVST_TYPE_OOS, *why); ast_db_put(db_chan_name, SRVST_DBKEY, db_answer); + sig_pri_span_devstate_changed(pri); break; default: ast_log(LOG_ERROR, "Huh? changestatus is: %d\n", e->service.changestatus); @@ -2301,7 +3051,12 @@ static void *pri_dchannel(void *vpri) pri_destroycall(pri->pri, e->ring.call); break; } - if (e->ring.channel == -1) + if (sig_pri_is_cis_call(e->ring.channel)) { + sig_pri_handle_cis_subcmds(pri, e->e, e->ring.subcmds, + e->ring.call); + break; + } + if (e->ring.channel == -1 || PRI_CHANNEL(e->ring.channel) == 0xFF) chanpos = pri_find_empty_chan(pri, 1); else chanpos = pri_find_principle(pri, e->ring.channel, e->ring.call); @@ -2644,6 +3399,11 @@ static void *pri_dchannel(void *vpri) } break; case PRI_EVENT_RINGING: + if (sig_pri_is_cis_call(e->ringing.channel)) { + sig_pri_handle_cis_subcmds(pri, e->e, e->ringing.subcmds, + e->ringing.call); + break; + } chanpos = pri_find_principle(pri, e->ringing.channel, e->ringing.call); if (chanpos < 0) { ast_log(LOG_WARNING, "Ringing requested on unconfigured channel %d/%d span %d\n", @@ -2658,6 +3418,7 @@ static void *pri_dchannel(void *vpri) sig_pri_handle_subcmds(pri, chanpos, e->e, e->ringing.channel, e->ringing.subcmds, e->ringing.call); + sig_pri_cc_generic_check(pri, chanpos, AST_CC_CCNR); sig_pri_set_echocanceller(pri->pvts[chanpos], 1); pri_queue_control(pri->pvts[chanpos], AST_CONTROL_RINGING, pri); pri->pvts[chanpos]->alerting = 1; @@ -2681,7 +3442,11 @@ static void *pri_dchannel(void *vpri) } break; case PRI_EVENT_PROGRESS: - /* Get chan value if e->e is not PRI_EVNT_RINGING */ + if (sig_pri_is_cis_call(e->proceeding.channel)) { + sig_pri_handle_cis_subcmds(pri, e->e, e->proceeding.subcmds, + e->proceeding.call); + break; + } chanpos = pri_find_principle(pri, e->proceeding.channel, e->proceeding.call); if (chanpos > -1) { sig_pri_lock_private(pri->pvts[chanpos]); @@ -2731,6 +3496,11 @@ static void *pri_dchannel(void *vpri) } break; case PRI_EVENT_PROCEEDING: + if (sig_pri_is_cis_call(e->proceeding.channel)) { + sig_pri_handle_cis_subcmds(pri, e->e, e->proceeding.subcmds, + e->proceeding.call); + break; + } chanpos = pri_find_principle(pri, e->proceeding.channel, e->proceeding.call); if (chanpos > -1) { sig_pri_lock_private(pri->pvts[chanpos]); @@ -2760,6 +3530,17 @@ static void *pri_dchannel(void *vpri) } break; case PRI_EVENT_FACILITY: + if (!e->facility.call || sig_pri_is_cis_call(e->facility.channel)) { + /* Event came in on the dummy channel or a CIS call. */ +#if defined(HAVE_PRI_CALL_REROUTING) + sig_pri_handle_cis_subcmds(pri, e->e, e->facility.subcmds, + e->facility.subcall); +#else + sig_pri_handle_cis_subcmds(pri, e->e, e->facility.subcmds, + e->facility.call); +#endif /* !defined(HAVE_PRI_CALL_REROUTING) */ + break; + } chanpos = pri_find_principle(pri, e->facility.channel, e->facility.call); if (chanpos < 0) { ast_log(LOG_WARNING, "Facility requested on unconfigured channel %d/%d span %d\n", @@ -2783,6 +3564,11 @@ static void *pri_dchannel(void *vpri) } break; case PRI_EVENT_ANSWER: + if (sig_pri_is_cis_call(e->answer.channel)) { + sig_pri_handle_cis_subcmds(pri, e->e, e->answer.subcmds, + e->answer.call); + break; + } chanpos = pri_find_principle(pri, e->answer.channel, e->answer.call); if (chanpos < 0) { ast_log(LOG_WARNING, "Answer on unconfigured channel %d/%d span %d\n", @@ -2821,6 +3607,12 @@ static void *pri_dchannel(void *vpri) } break; case PRI_EVENT_HANGUP: + if (sig_pri_is_cis_call(e->hangup.channel)) { + sig_pri_handle_cis_subcmds(pri, e->e, e->hangup.subcmds, + e->hangup.call); + pri_hangup(pri->pri, e->hangup.call, e->hangup.cause); + break; + } chanpos = pri_find_principle(pri, e->hangup.channel, e->hangup.call); if (chanpos < 0) { ast_log(LOG_WARNING, "Hangup requested on unconfigured channel %d/%d span %d\n", @@ -2834,6 +3626,14 @@ static void *pri_dchannel(void *vpri) if (!pri->pvts[chanpos]->alreadyhungup) { /* we're calling here dahdi_hangup so once we get there we need to clear p->call after calling pri_hangup */ pri->pvts[chanpos]->alreadyhungup = 1; + switch (e->hangup.cause) { + case PRI_CAUSE_USER_BUSY: + case PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION: + sig_pri_cc_generic_check(pri, chanpos, AST_CC_CCBS); + break; + default: + break; + } if (pri->pvts[chanpos]->owner) { /* Queue a BUSY instead of a hangup if our cause is appropriate */ pri->pvts[chanpos]->owner->hangupcause = e->hangup.cause; @@ -2900,6 +3700,12 @@ static void *pri_dchannel(void *vpri) } break; case PRI_EVENT_HANGUP_REQ: + if (sig_pri_is_cis_call(e->hangup.channel)) { + sig_pri_handle_cis_subcmds(pri, e->e, e->hangup.subcmds, + e->hangup.call); + pri_hangup(pri->pri, e->hangup.call, e->hangup.cause); + break; + } chanpos = pri_find_principle(pri, e->hangup.channel, e->hangup.call); if (chanpos < 0) { ast_log(LOG_WARNING, "Hangup REQ requested on unconfigured channel %d/%d span %d\n", @@ -2922,6 +3728,14 @@ static void *pri_dchannel(void *vpri) sig_pri_lock_private(pri->pvts[chanpos]); } #endif /* defined(HAVE_PRI_CALL_HOLD) */ + switch (e->hangup.cause) { + case PRI_CAUSE_USER_BUSY: + case PRI_CAUSE_NORMAL_CIRCUIT_CONGESTION: + sig_pri_cc_generic_check(pri, chanpos, AST_CC_CCBS); + break; + default: + break; + } if (pri->pvts[chanpos]->owner) { pri->pvts[chanpos]->owner->hangupcause = e->hangup.cause; switch (pri->pvts[chanpos]->owner->_state) { @@ -2984,6 +3798,11 @@ static void *pri_dchannel(void *vpri) } break; case PRI_EVENT_HANGUP_ACK: + if (sig_pri_is_cis_call(e->hangup.channel)) { + sig_pri_handle_cis_subcmds(pri, e->e, e->hangup.subcmds, + e->hangup.call); + break; + } chanpos = pri_find_principle(pri, e->hangup.channel, e->hangup.call); if (chanpos < 0) { ast_log(LOG_WARNING, "Hangup ACK requested on unconfigured channel number %d/%d span %d\n", @@ -3067,6 +3886,11 @@ static void *pri_dchannel(void *vpri) } break; case PRI_EVENT_SETUP_ACK: + if (sig_pri_is_cis_call(e->setup_ack.channel)) { + sig_pri_handle_cis_subcmds(pri, e->e, e->setup_ack.subcmds, + e->setup_ack.call); + break; + } chanpos = pri_find_principle(pri, e->setup_ack.channel, e->setup_ack.call); if (chanpos < 0) { ast_log(LOG_WARNING, "Received SETUP_ACKNOWLEDGE on unconfigured channel %d/%d span %d\n", @@ -3090,6 +3914,15 @@ static void *pri_dchannel(void *vpri) } break; case PRI_EVENT_NOTIFY: + if (sig_pri_is_cis_call(e->notify.channel)) { +#if defined(HAVE_PRI_CALL_HOLD) + sig_pri_handle_cis_subcmds(pri, e->e, e->notify.subcmds, + e->notify.call); +#else + sig_pri_handle_cis_subcmds(pri, e->e, e->notify.subcmds, NULL); +#endif /* !defined(HAVE_PRI_CALL_HOLD) */ + break; + } #if defined(HAVE_PRI_CALL_HOLD) chanpos = pri_find_principle(pri, e->notify.channel, e->notify.call); #else @@ -3130,6 +3963,7 @@ static void *pri_dchannel(void *vpri) break; #if defined(HAVE_PRI_CALL_HOLD) case PRI_EVENT_HOLD: + /* We should not be getting any CIS calls with this message type. */ if (sig_pri_handle_hold(pri, e)) { pri_hold_rej(pri->pri, e->hold.call, PRI_CAUSE_RESOURCE_UNAVAIL_UNSPECIFIED); @@ -3150,6 +3984,7 @@ static void *pri_dchannel(void *vpri) #endif /* defined(HAVE_PRI_CALL_HOLD) */ #if defined(HAVE_PRI_CALL_HOLD) case PRI_EVENT_RETRIEVE: + /* We should not be getting any CIS calls with this message type. */ sig_pri_handle_retrieve(pri, e); break; #endif /* defined(HAVE_PRI_CALL_HOLD) */ @@ -3189,7 +4024,7 @@ void sig_pri_init_pri(struct sig_pri_pri *pri) int sig_pri_hangup(struct sig_pri_chan *p, struct ast_channel *ast) { - int res = 0; + int res; #ifdef SUPPORT_USERUSER const char *useruser = pbx_builtin_getvar_helper(ast, "USERUSERINFO"); #endif @@ -3213,47 +4048,43 @@ int sig_pri_hangup(struct sig_pri_chan *p, struct ast_channel *ast) p->exten[0] = '\0'; sig_pri_set_dialing(p, 0); - if (!p->call) { - res = 0; - goto exit; - } - /* Make sure we have a call (or REALLY have a call in the case of a PRI) */ if (!pri_grab(p, p->pri)) { - if (p->alreadyhungup) { - ast_log(LOG_DEBUG, "Already hungup... Calling hangup once, and clearing call\n"); + if (p->call) { + if (p->alreadyhungup) { + ast_log(LOG_DEBUG, "Already hungup... Calling hangup once, and clearing call\n"); #ifdef SUPPORT_USERUSER - pri_call_set_useruser(p->call, useruser); + pri_call_set_useruser(p->call, useruser); #endif - pri_hangup(p->pri->pri, p->call, -1); - p->call = NULL; - } else { - const char *cause = pbx_builtin_getvar_helper(ast,"PRI_CAUSE"); - int icause = ast->hangupcause ? ast->hangupcause : -1; - ast_log(LOG_DEBUG, "Not yet hungup... Calling hangup once with icause, and clearing call\n"); + pri_hangup(p->pri->pri, p->call, -1); + p->call = NULL; + } else { + const char *cause = pbx_builtin_getvar_helper(ast,"PRI_CAUSE"); + int icause = ast->hangupcause ? ast->hangupcause : -1; + ast_log(LOG_DEBUG, "Not yet hungup... Calling hangup once with icause, and clearing call\n"); #ifdef SUPPORT_USERUSER - pri_call_set_useruser(p->call, useruser); + pri_call_set_useruser(p->call, useruser); #endif - p->alreadyhungup = 1; - if (cause) { - if (atoi(cause)) - icause = atoi(cause); + p->alreadyhungup = 1; + if (cause) { + if (atoi(cause)) + icause = atoi(cause); + } + pri_hangup(p->pri->pri, p->call, icause); } - pri_hangup(p->pri->pri, p->call, icause); } - if (res < 0) - ast_log(LOG_WARNING, "pri_disconnect failed\n"); + sig_pri_span_devstate_changed(p->pri); pri_rel(p->pri); + res = 0; } else { ast_log(LOG_WARNING, "Unable to grab PRI on span %d\n", p->pri->span); res = -1; } -exit: ast->tech_pvt = NULL; return res; } @@ -3356,6 +4187,7 @@ int sig_pri_call(struct sig_pri_chan *p, struct ast_channel *ast, char *rdest, i #ifdef SUPPORT_USERUSER const char *useruser; #endif + int core_id; int pridialplan; int dp_strip; int prilocaldialplan; @@ -3672,7 +4504,41 @@ int sig_pri_call(struct sig_pri_chan *p, struct ast_channel *ast, char *rdest, i pri_sr_set_useruser(sr, useruser); #endif - if (pri_setup(p->pri->pri, p->call, sr)) { +#if defined(HAVE_PRI_CCSS) + if (ast_cc_is_recall(ast, &core_id, sig_pri_cc_type_name)) { + struct ast_cc_monitor *monitor; + char device_name[AST_CHANNEL_NAME]; + + /* This is a CC recall call. */ + ast_channel_get_device_name(ast, device_name, sizeof(device_name)); + monitor = ast_cc_get_monitor_by_recall_core_id(core_id, device_name); + if (monitor) { + struct sig_pri_cc_monitor_instance *instance; + + instance = monitor->private_data; + + /* If this fails then we have monitor instance ambiguity. */ + ast_assert(p->pri == instance->pri); + + if (pri_cc_call(p->pri->pri, instance->cc_id, p->call, sr)) { + /* The CC recall call failed for some reason. */ + ast_log(LOG_WARNING, "Unable to setup CC recall call to device %s\n", + device_name); + ao2_ref(monitor, -1); + pri_rel(p->pri); + pri_sr_free(sr); + return -1; + } + ao2_ref(monitor, -1); + } else { + core_id = -1; + } + } else +#endif /* defined(HAVE_PRI_CCSS) */ + { + core_id = -1; + } + if (core_id == -1 && pri_setup(p->pri->pri, p->call, sr)) { ast_log(LOG_WARNING, "Unable to setup call to %s (using %s)\n", c + p->stripmsd + dp_strip, dialplan2str(p->pri->dialplan)); pri_rel(p->pri); @@ -3935,12 +4801,6 @@ int sig_pri_start_pri(struct sig_pri_pri *pri) #ifdef HAVE_PRI_INBANDDISCONNECT pri_set_inbanddisconnect(pri->dchans[i], pri->inbanddisconnect); #endif -#if defined(HAVE_PRI_CALL_HOLD) - pri_hold_enable(pri->dchans[i], 1); -#endif /* defined(HAVE_PRI_CALL_HOLD) */ -#if defined(HAVE_PRI_CALL_REROUTING) - pri_reroute_enable(pri->dchans[i], 1); -#endif /* defined(HAVE_PRI_CALL_REROUTING) */ /* Enslave to master if appropriate */ if (i) pri_enslave(pri->dchans[0], pri->dchans[i]); @@ -3951,7 +4811,7 @@ int sig_pri_start_pri(struct sig_pri_pri *pri) ast_log(LOG_ERROR, "Unable to create PRI structure\n"); return -1; } - pri_set_debug(pri->dchans[i], DEFAULT_PRI_DEBUG); + pri_set_debug(pri->dchans[i], SIG_PRI_DEBUG_DEFAULT); pri_set_nsf(pri->dchans[i], pri->nsf); #ifdef PRI_GETSET_TIMERS for (x = 0; x < PRI_MAX_TIMERS; x++) { @@ -3960,8 +4820,23 @@ int sig_pri_start_pri(struct sig_pri_pri *pri) } #endif } + /* Assume primary is the one we use */ pri->pri = pri->dchans[0]; + +#if defined(HAVE_PRI_CALL_HOLD) + pri_hold_enable(pri->pri, 1); +#endif /* defined(HAVE_PRI_CALL_HOLD) */ +#if defined(HAVE_PRI_CALL_REROUTING) + pri_reroute_enable(pri->pri, 1); +#endif /* defined(HAVE_PRI_CALL_REROUTING) */ +#if defined(HAVE_PRI_CCSS) + pri_cc_enable(pri->pri, 1); + pri_cc_recall_mode(pri->pri, pri->cc_ptmp_recall_mode); + pri_cc_retain_signaling_req(pri->pri, pri->cc_qsig_signaling_link_req); + pri_cc_retain_signaling_rsp(pri->pri, pri->cc_qsig_signaling_link_rsp); +#endif /* defined(HAVE_PRI_CCSS) */ + pri->resetpos = -1; if (ast_pthread_create_background(&pri->master, NULL, pri_dchannel, pri)) { for (i = 0; i < NUM_DCHANS; i++) { @@ -4003,161 +4878,758 @@ void sig_pri_chan_alarm_notify(struct sig_pri_chan *p, int noalarm) } } -struct sig_pri_chan *sig_pri_chan_new(void *pvt_data, struct sig_pri_callback *callback, struct sig_pri_pri *pri, int logicalspan, int channo, int trunkgroup) +struct sig_pri_chan *sig_pri_chan_new(void *pvt_data, struct sig_pri_callback *callback, struct sig_pri_pri *pri, int logicalspan, int channo, int trunkgroup) +{ + struct sig_pri_chan *p; + + p = ast_calloc(1, sizeof(*p)); + if (!p) + return p; + + p->logicalspan = logicalspan; + p->prioffset = channo; + p->mastertrunkgroup = trunkgroup; + + p->calls = callback; + p->chan_pvt = pvt_data; + + p->pri = pri; + + return p; +} + +/*! + * \brief Delete the sig_pri private channel structure. + * \since 1.8 + * + * \param doomed sig_pri private channel structure to delete. + * + * \return Nothing + */ +void sig_pri_chan_delete(struct sig_pri_chan *doomed) +{ + ast_free(doomed); +} + +static void build_status(char *s, size_t len, int status, int active) +{ + if (!s || len < 1) { + return; + } + s[0] = '\0'; + if (!(status & DCHAN_NOTINALARM)) + strncat(s, "In Alarm, ", len - strlen(s) - 1); + if (status & DCHAN_UP) + strncat(s, "Up", len - strlen(s) - 1); + else + strncat(s, "Down", len - strlen(s) - 1); + if (active) + strncat(s, ", Active", len - strlen(s) - 1); + else + strncat(s, ", Standby", len - strlen(s) - 1); + s[len - 1] = '\0'; +} + +void sig_pri_cli_show_spans(int fd, int span, struct sig_pri_pri *pri) +{ + char status[256]; + int x; + for (x = 0; x < NUM_DCHANS; x++) { + if (pri->dchans[x]) { + build_status(status, sizeof(status), pri->dchanavail[x], pri->dchans[x] == pri->pri); + ast_cli(fd, "PRI span %d/%d: %s\n", span, x, status); + } + } +} + +void sig_pri_cli_show_span(int fd, int *dchannels, struct sig_pri_pri *pri) +{ + int x; + char status[256]; + + for (x = 0; x < NUM_DCHANS; x++) { + if (pri->dchans[x]) { +#ifdef PRI_DUMP_INFO_STR + char *info_str = NULL; +#endif + ast_cli(fd, "%s D-channel: %d\n", pri_order(x), dchannels[x]); + build_status(status, sizeof(status), pri->dchanavail[x], pri->dchans[x] == pri->pri); + ast_cli(fd, "Status: %s\n", status); +#ifdef PRI_DUMP_INFO_STR + info_str = pri_dump_info_str(pri->pri); + if (info_str) { + ast_cli(fd, "%s", info_str); + free(info_str); + } +#else + pri_dump_info(pri->pri); +#endif + ast_cli(fd, "Overlap Recv: %s\n\n", (pri->overlapdial & DAHDI_OVERLAPDIAL_INCOMING)?"Yes":"No"); + ast_cli(fd, "\n"); + } + } +} + +int pri_send_keypad_facility_exec(struct sig_pri_chan *p, const char *digits) +{ + sig_pri_lock_private(p); + + if (!p->pri || !p->call) { + ast_debug(1, "Unable to find pri or call on channel!\n"); + sig_pri_unlock_private(p); + return -1; + } + + if (!pri_grab(p, p->pri)) { + pri_keypad_facility(p->pri->pri, p->call, digits); + pri_rel(p->pri); + } else { + ast_debug(1, "Unable to grab pri to send keypad facility!\n"); + sig_pri_unlock_private(p); + return -1; + } + + sig_pri_unlock_private(p); + + return 0; +} + +int pri_send_callrerouting_facility_exec(struct sig_pri_chan *p, enum ast_channel_state chanstate, const char *destination, const char *original, const char *reason) +{ + int res = -1; + + sig_pri_lock_private(p); + + if (!p->pri || !p->call) { + ast_log(LOG_DEBUG, "Unable to find pri or call on channel!\n"); + sig_pri_unlock_private(p); + return -1; + } + + if (!pri_grab(p, p->pri)) { + res = pri_callrerouting_facility(p->pri->pri, p->call, destination, original, reason); + pri_rel(p->pri); + } else { + ast_log(LOG_DEBUG, "Unable to grab pri to send callrerouting facility on span %d!\n", p->pri->span); + } + + sig_pri_unlock_private(p); + + return res; +} + +#if defined(HAVE_PRI_SERVICE_MESSAGES) +int pri_maintenance_bservice(struct pri *pri, struct sig_pri_chan *p, int changestatus) +{ + int channel = PVT_TO_CHANNEL(p); + int span = PRI_SPAN(channel); + + return pri_maintenance_service(pri, span, channel, changestatus); +} +#endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ + +void sig_pri_fixup(struct ast_channel *oldchan, struct ast_channel *newchan, struct sig_pri_chan *pchan) +{ + if (pchan->owner == oldchan) { + pchan->owner = newchan; + } +} + +#if defined(HAVE_PRI_CCSS) +/*! + * \brief PRI CC agent initialization. + * \since 1.8 + * + * \param agent CC core agent control. + * \param pvt_chan Original channel the agent will attempt to recall. + * + * \details + * This callback is called when the CC core is initialized. Agents should allocate + * any private data necessary for the call and assign it to the private_data + * on the agent. Additionally, if any ast_cc_agent_flags are pertinent to the + * specific agent type, they should be set in this function as well. + * + * \retval 0 on success. + * \retval -1 on error. + */ +int sig_pri_cc_agent_init(struct ast_cc_agent *agent, struct sig_pri_chan *pvt_chan) +{ + struct sig_pri_cc_agent_prv *cc_pvt; + + cc_pvt = ast_calloc(1, sizeof(*cc_pvt)); + if (!cc_pvt) { + return -1; + } + + ast_mutex_lock(&pvt_chan->pri->lock); + cc_pvt->pri = pvt_chan->pri; + cc_pvt->cc_id = pri_cc_available(pvt_chan->pri->pri, pvt_chan->call); + ast_mutex_unlock(&pvt_chan->pri->lock); + if (cc_pvt->cc_id == -1) { + ast_free(cc_pvt); + return -1; + } + agent->private_data = cc_pvt; + return 0; +} +#endif /* defined(HAVE_PRI_CCSS) */ + +#if defined(HAVE_PRI_CCSS) +/*! + * \brief Start the offer timer. + * \since 1.8 + * + * \param agent CC core agent control. + * + * \details + * This is called by the core when the caller hangs up after + * a call for which CC may be requested. The agent should + * begin the timer as configured. + * + * The primary reason why this functionality is left to + * the specific agent implementations is due to the differing + * use of schedulers throughout the code. Some channel drivers + * may already have a scheduler context they wish to use, and + * amongst those, some may use the ast_sched API while others + * may use the ast_sched_thread API, which are incompatible. + * + * \retval 0 on success. + * \retval -1 on error. + */ +int sig_pri_cc_agent_start_offer_timer(struct ast_cc_agent *agent) +{ + /* libpri maintains it's own offer timer in the form of T_RETENTION. */ + return 0; +} +#endif /* defined(HAVE_PRI_CCSS) */ + +#if defined(HAVE_PRI_CCSS) +/*! + * \brief Stop the offer timer. + * \since 1.8 + * + * \param agent CC core agent control. + * + * \details + * This callback is called by the CC core when the caller + * has requested CC. + * + * \retval 0 on success. + * \retval -1 on error. + */ +int sig_pri_cc_agent_stop_offer_timer(struct ast_cc_agent *agent) +{ + /* libpri maintains it's own offer timer in the form of T_RETENTION. */ + return 0; +} +#endif /* defined(HAVE_PRI_CCSS) */ + +#if defined(HAVE_PRI_CCSS) +/*! + * \brief Acknowledge CC request. + * \since 1.8 + * + * \param agent CC core agent control. + * + * \details + * When the core receives knowledge that a called + * party has accepted a CC request, it will call + * this callback. + * + * The duty of this is to accept a CC request from + * the caller by acknowledging receipt of that request. + * + * \return Nothing + */ +void sig_pri_cc_agent_req_ack(struct ast_cc_agent *agent) +{ + struct sig_pri_cc_agent_prv *cc_pvt; + int res; + + cc_pvt = agent->private_data; + ast_mutex_lock(&cc_pvt->pri->lock); + if (cc_pvt->cc_request_response_pending) { + cc_pvt->cc_request_response_pending = 0; + res = pri_cc_req_rsp(cc_pvt->pri->pri, cc_pvt->cc_id, 0/* success */); + } else { + res = 0; + } + ast_mutex_unlock(&cc_pvt->pri->lock); + if (res) { + ast_cc_failed(agent->core_id, "%s agent failed to send the CC request ack.", + sig_pri_cc_type_name); + } +} +#endif /* defined(HAVE_PRI_CCSS) */ + +#if defined(HAVE_PRI_CCSS) +/*! + * \brief Request the status of the agent's device. + * \since 1.8 + * + * \param agent CC core agent control. + * + * \details + * Asynchronous request for the status of any caller + * which may be a valid caller for the CC transaction. + * Status responses should be made using the + * ast_cc_status_response function. + * + * \retval 0 on success. + * \retval -1 on error. + */ +int sig_pri_cc_agent_status_req(struct ast_cc_agent *agent) { - struct sig_pri_chan *p; + struct sig_pri_cc_agent_prv *cc_pvt; - p = ast_calloc(1, sizeof(*p)); - if (!p) - return p; + cc_pvt = agent->private_data; + ast_mutex_lock(&cc_pvt->pri->lock); + pri_cc_status_req(cc_pvt->pri->pri, cc_pvt->cc_id); + ast_mutex_unlock(&cc_pvt->pri->lock); + return 0; +} +#endif /* defined(HAVE_PRI_CCSS) */ - p->logicalspan = logicalspan; - p->prioffset = channo; - p->mastertrunkgroup = trunkgroup; +#if defined(HAVE_PRI_CCSS) +/*! + * \brief Request for an agent's phone to stop ringing. + * \since 1.8 + * + * \param agent CC core agent control. + * + * \details + * The usefulness of this is quite limited. The only specific + * known case for this is if Asterisk requests CC over an ISDN + * PTMP link as the TE side. If other phones are in the same + * recall group as the Asterisk server, and one of those phones + * picks up the recall notice, then Asterisk will receive a + * "stop ringing" notification from the NT side of the PTMP + * link. This indication needs to be passed to the phone + * on the other side of the Asterisk server which originally + * placed the call so that it will stop ringing. Since the + * phone may be of any type, it is necessary to have a callback + * that the core can know about. + * + * \retval 0 on success. + * \retval -1 on error. + */ +int sig_pri_cc_agent_stop_ringing(struct ast_cc_agent *agent) +{ + struct sig_pri_cc_agent_prv *cc_pvt; - p->calls = callback; - p->chan_pvt = pvt_data; + cc_pvt = agent->private_data; + ast_mutex_lock(&cc_pvt->pri->lock); + pri_cc_stop_alerting(cc_pvt->pri->pri, cc_pvt->cc_id); + ast_mutex_unlock(&cc_pvt->pri->lock); + return 0; +} +#endif /* defined(HAVE_PRI_CCSS) */ - p->pri = pri; +#if defined(HAVE_PRI_CCSS) +/*! + * \brief Let the caller know that the callee has become free + * but that the caller cannot attempt to call back because + * he is either busy or there is congestion on his line. + * \since 1.8 + * + * \param agent CC core agent control. + * + * \details + * This is something that really only affects a scenario where + * a phone places a call over ISDN PTMP to Asterisk, who then + * connects over PTMP again to the ISDN network. For most agent + * types, there is no need to implement this callback at all + * because they don't really need to actually do anything in + * this situation. If you're having trouble understanding what + * the purpose of this callback is, then you can be safe simply + * not implementing it. + * + * \retval 0 on success. + * \retval -1 on error. + */ +int sig_pri_cc_agent_party_b_free(struct ast_cc_agent *agent) +{ + struct sig_pri_cc_agent_prv *cc_pvt; - return p; + cc_pvt = agent->private_data; + ast_mutex_lock(&cc_pvt->pri->lock); + pri_cc_b_free(cc_pvt->pri->pri, cc_pvt->cc_id); + ast_mutex_unlock(&cc_pvt->pri->lock); + return 0; } +#endif /* defined(HAVE_PRI_CCSS) */ +#if defined(HAVE_PRI_CCSS) /*! - * \brief Delete the sig_pri private channel structure. + * \brief Begin monitoring a busy device. * \since 1.8 * - * \param doomed sig_pri private channel structure to delete. + * \param agent CC core agent control. * - * \return Nothing + * \details + * The core will call this callback if the callee becomes + * available but the caller has reported that he is busy. + * The agent should begin monitoring the caller's device. + * When the caller becomes available again, the agent should + * call ast_cc_agent_caller_available. + * + * \retval 0 on success. + * \retval -1 on error. */ -void sig_pri_chan_delete(struct sig_pri_chan *doomed) +int sig_pri_cc_agent_start_monitoring(struct ast_cc_agent *agent) { - ast_free(doomed); + /* libpri already knows when and how it needs to monitor Party A. */ + return 0; } +#endif /* defined(HAVE_PRI_CCSS) */ -static void build_status(char *s, size_t len, int status, int active) +#if defined(HAVE_PRI_CCSS) +/*! + * \brief Alert the caller that it is time to try recalling. + * \since 1.8 + * + * \param agent CC core agent control. + * + * \details + * The core will call this function when it receives notice + * that a monitored party has become available. + * + * The agent's job is to send a message to the caller to + * notify it of such a change. If the agent is able to + * discern that the caller is currently unavailable, then + * the agent should react by calling the ast_cc_caller_unavailable + * function. + * + * \retval 0 on success. + * \retval -1 on error. + */ +int sig_pri_cc_agent_callee_available(struct ast_cc_agent *agent) { - if (!s || len < 1) { + struct sig_pri_cc_agent_prv *cc_pvt; + + cc_pvt = agent->private_data; + ast_mutex_lock(&cc_pvt->pri->lock); + pri_cc_remote_user_free(cc_pvt->pri->pri, cc_pvt->cc_id); + ast_mutex_unlock(&cc_pvt->pri->lock); + return 0; +} +#endif /* defined(HAVE_PRI_CCSS) */ + +#if defined(HAVE_PRI_CCSS) +/*! + * \brief Destroy private data on the agent. + * \since 1.8 + * + * \param agent CC core agent control. + * + * \details + * The core will call this function upon completion + * or failure of CC. + * + * \note + * The agent private_data pointer may be NULL if the agent + * constructor failed. + * + * \return Nothing + */ +void sig_pri_cc_agent_destructor(struct ast_cc_agent *agent) +{ + struct sig_pri_cc_agent_prv *cc_pvt; + int res; + + cc_pvt = agent->private_data; + if (!cc_pvt) { + /* The agent constructor probably failed. */ return; } - s[0] = '\0'; - if (!(status & DCHAN_NOTINALARM)) - strncat(s, "In Alarm, ", len - strlen(s) - 1); - if (status & DCHAN_UP) - strncat(s, "Up", len - strlen(s) - 1); - else - strncat(s, "Down", len - strlen(s) - 1); - if (active) - strncat(s, ", Active", len - strlen(s) - 1); - else - strncat(s, ", Standby", len - strlen(s) - 1); - s[len - 1] = '\0'; + ast_mutex_lock(&cc_pvt->pri->lock); + res = -1; + if (cc_pvt->cc_request_response_pending) { + res = pri_cc_req_rsp(cc_pvt->pri->pri, cc_pvt->cc_id, 2/* short_term_denial */); + } + if (res) { + pri_cc_cancel(cc_pvt->pri->pri, cc_pvt->cc_id); + } + ast_mutex_unlock(&cc_pvt->pri->lock); + ast_free(cc_pvt); } +#endif /* defined(HAVE_PRI_CCSS) */ -void sig_pri_cli_show_spans(int fd, int span, struct sig_pri_pri *pri) +#if defined(HAVE_PRI_CCSS) +/*! + * \internal + * \brief Return the hash value of the given CC monitor instance object. + * \since 1.8 + * + * \param obj pointer to the (user-defined part) of an object. + * \param flags flags from ao2_callback(). Ignored at the moment. + * + * \retval core_id + */ +static int sig_pri_cc_monitor_instance_hash_fn(const void *obj, const int flags) { - char status[256]; - int x; - for (x = 0; x < NUM_DCHANS; x++) { - if (pri->dchans[x]) { - build_status(status, sizeof(status), pri->dchanavail[x], pri->dchans[x] == pri->pri); - ast_cli(fd, "PRI span %d/%d: %s\n", span, x, status); - } - } + const struct sig_pri_cc_monitor_instance *monitor_instance = obj; + + return monitor_instance->core_id; } +#endif /* defined(HAVE_PRI_CCSS) */ -void sig_pri_cli_show_span(int fd, int *dchannels, struct sig_pri_pri *pri) +#if defined(HAVE_PRI_CCSS) +/*! + * \internal + * \brief Compere the monitor instance core_id key value. + * \since 1.8 + * + * \param obj pointer to the (user-defined part) of an object. + * \param arg callback argument from ao2_callback() + * \param flags flags from ao2_callback() + * + * \return values are a combination of enum _cb_results. + */ +static int sig_pri_cc_monitor_instance_cmp_fn(void *obj, void *arg, int flags) { - int x; - char status[256]; + struct sig_pri_cc_monitor_instance *monitor_1 = obj; + struct sig_pri_cc_monitor_instance *monitor_2 = arg; - for (x = 0; x < NUM_DCHANS; x++) { - if (pri->dchans[x]) { -#ifdef PRI_DUMP_INFO_STR - char *info_str = NULL; -#endif - ast_cli(fd, "%s D-channel: %d\n", pri_order(x), dchannels[x]); - build_status(status, sizeof(status), pri->dchanavail[x], pri->dchans[x] == pri->pri); - ast_cli(fd, "Status: %s\n", status); -#ifdef PRI_DUMP_INFO_STR - info_str = pri_dump_info_str(pri->pri); - if (info_str) { - ast_cli(fd, "%s", info_str); - free(info_str); - } -#else - pri_dump_info(pri->pri); -#endif - ast_cli(fd, "Overlap Recv: %s\n\n", (pri->overlapdial & DAHDI_OVERLAPDIAL_INCOMING)?"Yes":"No"); - ast_cli(fd, "\n"); - } - } + return monitor_1->core_id == monitor_2->core_id ? CMP_MATCH | CMP_STOP : 0; } +#endif /* defined(HAVE_PRI_CCSS) */ -int pri_send_keypad_facility_exec(struct sig_pri_chan *p, const char *digits) +#if defined(HAVE_PRI_CCSS) +/*! + * \brief Request CCSS. + * \since 1.8 + * + * \param monitor CC core monitor control. + * \param available_timer_id Where to put the available timer scheduler id. + * Will never be NULL for a device monitor. + * + * \details + * Perform whatever steps are necessary in order to request CC. + * In addition, the monitor implementation is responsible for + * starting the available timer in this callback. The scheduler + * ID for the callback must be stored in the parent_link's child_avail_id + * field. + * + * \retval 0 on success + * \retval -1 on failure. + */ +int sig_pri_cc_monitor_req_cc(struct ast_cc_monitor *monitor, int *available_timer_id) { - sig_pri_lock_private(p); + struct sig_pri_cc_monitor_instance *instance; + int cc_mode; + int res; - if (!p->pri || !p->call) { - ast_debug(1, "Unable to find pri or call on channel!\n"); - sig_pri_unlock_private(p); + switch (monitor->service_offered) { + case AST_CC_CCBS: + cc_mode = 0;/* CCBS */ + break; + case AST_CC_CCNR: + cc_mode = 1;/* CCNR */ + break; + default: + /* CC service not supported by ISDN. */ return -1; } - if (!pri_grab(p, p->pri)) { - pri_keypad_facility(p->pri->pri, p->call, digits); - pri_rel(p->pri); - } else { - ast_debug(1, "Unable to grab pri to send keypad facility!\n"); - sig_pri_unlock_private(p); - return -1; - } + instance = monitor->private_data; - sig_pri_unlock_private(p); + /* libpri handles it's own available timer. */ + ast_mutex_lock(&instance->pri->lock); + res = pri_cc_req(instance->pri->pri, instance->cc_id, cc_mode); + ast_mutex_unlock(&instance->pri->lock); + + return res; +} +#endif /* defined(HAVE_PRI_CCSS) */ + +#if defined(HAVE_PRI_CCSS) +/*! + * \brief Suspend monitoring. + * \since 1.8 + * + * \param monitor CC core monitor control. + * + * \details + * Implementers must perform the necessary steps to suspend + * monitoring. + * + * \retval 0 on success + * \retval -1 on failure. + */ +int sig_pri_cc_monitor_suspend(struct ast_cc_monitor *monitor) +{ + struct sig_pri_cc_monitor_instance *instance; + + instance = monitor->private_data; + ast_mutex_lock(&instance->pri->lock); + pri_cc_status(instance->pri->pri, instance->cc_id, 1/* busy */); + ast_mutex_unlock(&instance->pri->lock); return 0; } +#endif /* defined(HAVE_PRI_CCSS) */ -int pri_send_callrerouting_facility_exec(struct sig_pri_chan *p, enum ast_channel_state chanstate, const char *destination, const char *original, const char *reason) +#if defined(HAVE_PRI_CCSS) +/*! + * \brief Unsuspend monitoring. + * \since 1.8 + * + * \param monitor CC core monitor control. + * + * \details + * Perform the necessary steps to unsuspend monitoring. + * + * \retval 0 on success + * \retval -1 on failure. + */ +int sig_pri_cc_monitor_unsuspend(struct ast_cc_monitor *monitor) { - int res = -1; + struct sig_pri_cc_monitor_instance *instance; - sig_pri_lock_private(p); + instance = monitor->private_data; + ast_mutex_lock(&instance->pri->lock); + pri_cc_status(instance->pri->pri, instance->cc_id, 0/* free */); + ast_mutex_unlock(&instance->pri->lock); - if (!p->pri || !p->call) { - ast_log(LOG_DEBUG, "Unable to find pri or call on channel!\n"); - sig_pri_unlock_private(p); - return -1; - } + return 0; +} +#endif /* defined(HAVE_PRI_CCSS) */ - if (!pri_grab(p, p->pri)) { - res = pri_callrerouting_facility(p->pri->pri, p->call, destination, original, reason); - pri_rel(p->pri); - } else { - ast_log(LOG_DEBUG, "Unable to grab pri to send callrerouting facility on span %d!\n", p->pri->span); +#if defined(HAVE_PRI_CCSS) +/*! + * \brief Status response to an ast_cc_monitor_status_request(). + * \since 1.8 + * + * \param monitor CC core monitor control. + * \param devstate Current status of a Party A device. + * + * \details + * Alert a monitor as to the status of the agent for which + * the monitor had previously requested a status request. + * + * \note Zero or more responses may come as a result. + * + * \retval 0 on success + * \retval -1 on failure. + */ +int sig_pri_cc_monitor_status_rsp(struct ast_cc_monitor *monitor, enum ast_device_state devstate) +{ + struct sig_pri_cc_monitor_instance *instance; + int cc_status; + + switch (devstate) { + case AST_DEVICE_UNKNOWN: + case AST_DEVICE_NOT_INUSE: + cc_status = 0;/* free */ + break; + case AST_DEVICE_BUSY: + case AST_DEVICE_INUSE: + cc_status = 1;/* busy */ + break; + default: + /* Don't know how to interpret this device state into free/busy status. */ + return 0; } + instance = monitor->private_data; + ast_mutex_lock(&instance->pri->lock); + pri_cc_status_req_rsp(instance->pri->pri, instance->cc_id, cc_status); + ast_mutex_unlock(&instance->pri->lock); - sig_pri_unlock_private(p); + return 0; +} +#endif /* defined(HAVE_PRI_CCSS) */ - return res; +#if defined(HAVE_PRI_CCSS) +/*! + * \brief Cancel the running available timer. + * \since 1.8 + * + * \param monitor CC core monitor control. + * \param sched_id Available timer scheduler id to cancel. + * Will never be NULL for a device monitor. + * + * \details + * In most cases, this function will likely consist of just a + * call to AST_SCHED_DEL. It might have been possible to do this + * within the core, but unfortunately the mixture of sched_thread + * and sched usage in Asterisk prevents such usage. + * + * \retval 0 on success + * \retval -1 on failure. + */ +int sig_pri_cc_monitor_cancel_available_timer(struct ast_cc_monitor *monitor, int *sched_id) +{ + /* + * libpri maintains it's own available timer as one of: + * T_CCBS2/T_CCBS5/T_CCBS6/QSIG_CCBS_T2 + * T_CCNR2/T_CCNR5/T_CCNR6/QSIG_CCNR_T2 + */ + return 0; } +#endif /* defined(HAVE_PRI_CCSS) */ -#if defined(HAVE_PRI_SERVICE_MESSAGES) -int pri_maintenance_bservice(struct pri *pri, struct sig_pri_chan *p, int changestatus) +#if defined(HAVE_PRI_CCSS) +/*! + * \brief Destroy PRI private data on the monitor. + * \since 1.8 + * + * \param monitor_pvt CC device monitor private data pointer. + * + * \details + * Implementers of this callback are responsible for destroying + * all heap-allocated data in the monitor's private_data pointer, including + * the private_data itself. + */ +void sig_pri_cc_monitor_destructor(void *monitor_pvt) { - int channel = PVT_TO_CHANNEL(p); - int span = PRI_SPAN(channel); + struct sig_pri_cc_monitor_instance *instance; - return pri_maintenance_service(pri, span, channel, changestatus); + instance = monitor_pvt; + if (!instance) { + return; + } + ao2_unlink(sig_pri_cc_monitors, instance); + ao2_ref(instance, -1); } -#endif /* defined(HAVE_PRI_SERVICE_MESSAGES) */ +#endif /* defined(HAVE_PRI_CCSS) */ -void sig_pri_fixup(struct ast_channel *oldchan, struct ast_channel *newchan, struct sig_pri_chan *pchan) +/*! + * \brief Load the sig_pri submodule. + * \since 1.8 + * + * \param cc_type_name CC type name to use when looking up agent/monitor. + * + * \retval 0 on success. + * \retval -1 on error. + */ +int sig_pri_load(const char *cc_type_name) { - if (pchan->owner == oldchan) { - pchan->owner = newchan; +#if defined(HAVE_PRI_CCSS) + sig_pri_cc_type_name = cc_type_name; + sig_pri_cc_monitors = ao2_container_alloc(37, sig_pri_cc_monitor_instance_hash_fn, + sig_pri_cc_monitor_instance_cmp_fn); + if (!sig_pri_cc_monitors) { + return -1; + } +#endif /* defined(HAVE_PRI_CCSS) */ + return 0; +} + +/*! + * \brief Unload the sig_pri submodule. + * \since 1.8 + * + * \return Nothing + */ +void sig_pri_unload(void) +{ +#if defined(HAVE_PRI_CCSS) + if (sig_pri_cc_monitors) { + ao2_ref(sig_pri_cc_monitors, -1); + sig_pri_cc_monitors = NULL; } +#endif /* defined(HAVE_PRI_CCSS) */ } #endif /* HAVE_PRI */ diff --git a/channels/sig_pri.h b/channels/sig_pri.h index 0bccd6ab0..7ea92d752 100644 --- a/channels/sig_pri.h +++ b/channels/sig_pri.h @@ -27,8 +27,44 @@ #include "asterisk/channel.h" #include "asterisk/frame.h" +#include "asterisk/ccss.h" #include #include +#if defined(PRI_SUBCMD_CC_AVAILABLE) +/* BUGBUG the HAVE_PRI_CCSS line is to be removed when the CCSS branch is merged to trunk and the configure script is updated. */ +#define HAVE_PRI_CCSS 1 +#endif /* defined(PRI_SUBCMD_CC_AVAILABLE) */ + +#if defined(HAVE_PRI_CCSS) +/*! PRI debug message flags when normal PRI debugging is turned on at the command line. */ +#define SIG_PRI_DEBUG_NORMAL \ + (PRI_DEBUG_APDU | PRI_DEBUG_Q931_DUMP | PRI_DEBUG_Q931_STATE | PRI_DEBUG_Q921_STATE \ + | PRI_DEBUG_CC) + +/*! PRI debug message flags when intense PRI debugging is turned on at the command line. */ +#define SIG_PRI_DEBUG_INTENSE \ + (PRI_DEBUG_APDU | PRI_DEBUG_Q931_DUMP | PRI_DEBUG_Q931_STATE | PRI_DEBUG_Q921_STATE \ + | PRI_DEBUG_CC | PRI_DEBUG_Q921_RAW | PRI_DEBUG_Q921_DUMP) + +#else + +/*! PRI debug message flags when normal PRI debugging is turned on at the command line. */ +#define SIG_PRI_DEBUG_NORMAL \ + (PRI_DEBUG_APDU | PRI_DEBUG_Q931_DUMP | PRI_DEBUG_Q931_STATE | PRI_DEBUG_Q921_STATE) + +/*! PRI debug message flags when intense PRI debugging is turned on at the command line. */ +#define SIG_PRI_DEBUG_INTENSE \ + (PRI_DEBUG_APDU | PRI_DEBUG_Q931_DUMP | PRI_DEBUG_Q931_STATE | PRI_DEBUG_Q921_STATE \ + | PRI_DEBUG_Q921_RAW | PRI_DEBUG_Q921_DUMP) +#endif /* !defined(HAVE_PRI_CCSS) */ + +#if 0 +/*! PRI debug message flags set on initial startup. */ +#define SIG_PRI_DEBUG_DEFAULT SIG_PRI_DEBUG_NORMAL +#else +/*! PRI debug message flags set on initial startup. */ +#define SIG_PRI_DEBUG_DEFAULT 0 +#endif enum sig_pri_tone { SIG_PRI_TONE_RINGTONE = 0, @@ -78,6 +114,14 @@ struct sig_pri_callback { void (* const set_rdnis)(void *pvt, const char *rdnis); void (* const queue_control)(void *pvt, int subclass); int (* const new_nobch_intf)(struct sig_pri_pri *pri); + const char *(* const get_orig_dialstring)(void *pvt); + void (* const make_cc_dialstring)(void *pvt, char *buf, size_t buf_size); + void (* const update_span_devstate)(struct sig_pri_pri *pri); + + /*! Reference the parent module. */ + void (*module_ref)(void); + /*! Unreference the parent module. */ + void (*module_unref)(void); }; #define NUM_DCHANS 4 /*!< No more than 4 d-channels */ @@ -194,6 +238,7 @@ struct sig_pri_chan { struct sig_pri_pri { /* Should be set by user */ + struct ast_cc_config_params *cc_params; /*!< CC config parameters for each new call. */ int pritimers[PRI_MAX_TIMERS]; int overlapdial; /*!< In overlap dialing mode */ int qsigchannelmapping; /*!< QSIG channel mapping type */ @@ -229,6 +274,11 @@ struct sig_pri_pri { int switchtype; /*!< Type of switch to emulate */ int nsf; /*!< Network-Specific Facilities */ int trunkgroup; /*!< What our trunkgroup is */ +#if defined(HAVE_PRI_CCSS) + int cc_ptmp_recall_mode; /*!< CC PTMP recall mode. globalRecall(0), specificRecall(1) */ + int cc_qsig_signaling_link_req; /*!< CC Q.SIG signaling link retention (Party A) release(0), retain(1), do-not-care(2) */ + int cc_qsig_signaling_link_rsp; /*!< CC Q.SIG signaling link retention (Party B) release(0), retain(1) */ +#endif /* defined(HAVE_PRI_CCSS) */ int dchanavail[NUM_DCHANS]; /*!< Whether each channel is available */ int debug; /*!< set to true if to dump PRI event info (tested but never set) */ @@ -257,6 +307,37 @@ struct sig_pri_pri { ast_mutex_t lock; /*!< libpri access Mutex */ time_t lastreset; /*!< time when unused channels were last reset */ struct sig_pri_callback *calls; + /*! + * \brief Congestion device state of the span. + * \details + * AST_DEVICE_NOT_INUSE - Span does not have all B channels in use. + * AST_DEVICE_BUSY - All B channels are in use. + * AST_DEVICE_UNAVAILABLE - Span is in alarm. + * \note + * Device name: DAHDI/I/congestion + */ + int congestion_devstate; +#if defined(THRESHOLD_DEVSTATE_PLACEHOLDER) + /*! \todo An ISDN span threshold device state could be useful in determining how often a span utilization goes over a configurable threshold. */ + /*! + * \brief User threshold device state of the span. + * \details + * AST_DEVICE_NOT_INUSE - There are no B channels in use. + * AST_DEVICE_INUSE - The number of B channels in use is less than + * the configured threshold but not zero. + * AST_DEVICE_BUSY - The number of B channels in use meets or exceeds + * the configured threshold. + * AST_DEVICE_UNAVAILABLE - Span is in alarm. + * \note + * Device name: DAHDI/I/threshold + */ + int threshold_devstate; + /*! + * \brief Number of B channels in use to consider the span in a busy state. + * \note Setting the threshold to zero is interpreted as all B channels. + */ + int user_busy_threshold; +#endif /* defined(THRESHOLD_DEVSTATE_PLACEHOLDER) */ }; void sig_pri_extract_called_num_subaddr(struct sig_pri_chan *p, const char *rdest, char *called, size_t called_buff_size); @@ -304,4 +385,25 @@ int pri_maintenance_bservice(struct pri *pri, struct sig_pri_chan *p, int change void sig_pri_fixup(struct ast_channel *oldchan, struct ast_channel *newchan, struct sig_pri_chan *pchan); +int sig_pri_cc_agent_init(struct ast_cc_agent *agent, struct sig_pri_chan *pvt_chan); +int sig_pri_cc_agent_start_offer_timer(struct ast_cc_agent *agent); +int sig_pri_cc_agent_stop_offer_timer(struct ast_cc_agent *agent); +void sig_pri_cc_agent_req_ack(struct ast_cc_agent *agent); +int sig_pri_cc_agent_status_req(struct ast_cc_agent *agent); +int sig_pri_cc_agent_stop_ringing(struct ast_cc_agent *agent); +int sig_pri_cc_agent_party_b_free(struct ast_cc_agent *agent); +int sig_pri_cc_agent_start_monitoring(struct ast_cc_agent *agent); +int sig_pri_cc_agent_callee_available(struct ast_cc_agent *agent); +void sig_pri_cc_agent_destructor(struct ast_cc_agent *agent); + +int sig_pri_cc_monitor_req_cc(struct ast_cc_monitor *monitor, int *available_timer_id); +int sig_pri_cc_monitor_suspend(struct ast_cc_monitor *monitor); +int sig_pri_cc_monitor_unsuspend(struct ast_cc_monitor *monitor); +int sig_pri_cc_monitor_status_rsp(struct ast_cc_monitor *monitor, enum ast_device_state devstate); +int sig_pri_cc_monitor_cancel_available_timer(struct ast_cc_monitor *monitor, int *sched_id); +void sig_pri_cc_monitor_destructor(void *monitor_pvt); + +int sig_pri_load(const char *cc_type_name); +void sig_pri_unload(void); + #endif /* _SIG_PRI_H */ diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h index ce87f0f23..1d900eb58 100644 --- a/channels/sip/include/sip.h +++ b/channels/sip/include/sip.h @@ -153,7 +153,7 @@ * \todo This string should be set dynamically. We only support REFER and SUBSCRIBE if we have * allowsubscribe and allowrefer on in sip.conf. */ -#define ALLOWED_METHODS "INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO" +#define ALLOWED_METHODS "INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH" /*! \brief SIP Extensions we support * \note This should be generated based on the previous array @@ -239,6 +239,7 @@ */ /*@{*/ #define SIP_OUTGOING (1 << 0) /*!< D: Direction of the last transaction in this dialog */ +#define SIP_OFFER_CC (1 << 1) /*!< D: Offer CC on subsequent responses */ #define SIP_RINGING (1 << 2) /*!< D: Have sent 180 ringing */ #define SIP_PROGRESS_SENT (1 << 3) /*!< D: Have sent 183 message progress */ #define SIP_NEEDREINVITE (1 << 4) /*!< D: Do we need to send another reinvite? */ @@ -415,7 +416,8 @@ enum subscriptiontype { DIALOG_INFO_XML, CPIM_PIDF_XML, PIDF_XML, - MWI_NOTIFICATION + MWI_NOTIFICATION, + CALL_COMPLETION, }; /*! \brief The number of media types in enum \ref media_type below. */ @@ -930,6 +932,7 @@ struct sip_pvt { AST_STRING_FIELD(url); /*!< URL to be sent with next message to peer */ AST_STRING_FIELD(parkinglot); /*!< Parkinglot */ AST_STRING_FIELD(engine); /*!< RTP engine to use */ + AST_STRING_FIELD(dialstring); /*!< The dialstring used to call this SIP endpoint */ ); char via[128]; /*!< Via: header */ struct sip_socket socket; /*!< The socket used for this dialog */ @@ -1066,6 +1069,8 @@ struct sip_pvt { * The large-scale changes would be a good idea for implementing during an SDP rewrite. */ struct offered_media offered_media[OFFERED_MEDIA_COUNT]; + struct ast_cc_config_params *cc_params; + struct sip_epa_entry *epa_entry; }; /*! \brief sip packet - raw format for outbound packets that are sent or scheduled for transmission @@ -1197,6 +1202,7 @@ struct sip_peer { /*XXX Seems like we suddenly have two flags with the same content. Why? To be continued... */ enum sip_peer_type type; /*!< Distinguish between "user" and "peer" types. This is used solely for CLI and manager commands */ unsigned int disallowed_methods; + struct ast_cc_config_params *cc_params; }; /*! @@ -1286,4 +1292,359 @@ struct sip_subscription_mwi { struct ast_dnsmgr_entry *dnsmgr; /*!< DNS refresh manager for subscription */ struct sockaddr_in us; /*!< Who the server thinks we are */ }; + +/*! + * SIP PUBLISH support! + * PUBLISH support was added to chan_sip due to its use in the call-completion + * event package. In order to suspend and unsuspend monitoring of a called party, + * a PUBLISH message must be sent. Rather than try to hack in PUBLISH transmission + * and reception solely for the purposes of handling call-completion-related messages, + * an effort has been made to create a generic framework for handling PUBLISH messages. + * + * There are two main components to the effort, the event publication agent (EPA) and + * the event state compositor (ESC). Both of these terms appear in RFC 3903, and the + * implementation in Asterisk conforms to the defintions there. An EPA is a UAC that + * transmits PUBLISH requests. An ESC is a UAS that receives PUBLISH requests and + * acts appropriately based on the content of those requests. + * + * ESC: + * The main structure in chan_sip is the event_state_compositor. There is an + * event_state_compositor structure for each event package supported (as of Nov 2009 + * this is only the call-completion package). The structure contains data which is + * intrinsic to the event package itself, such as the name of the package and a set + * of callbacks for handling incoming PUBLISH requests. In addition, the + * event_state_compositor struct contains an ao2_container of sip_esc_entries. + * + * A sip_esc_entry corresponds to an entity which has sent a PUBLISH to Asterisk. We are + * able to match the incoming PUBLISH to a sip_esc_entry using the Sip-If-Match header + * of the message. Of course, if none is present, then a new sip_esc_entry will be created. + * + * Once it is determined what type of PUBLISH request has come in (from RFC 3903, it may + * be an initial, modify, refresh, or remove), then the event package-specific callbacks + * may be called. If your event package doesn't need to take any specific action for a + * specific PUBLISH type, it is perfectly safe to not define the callback at all. The callback + * only needs to take care of application-specific information. If there is a problem, it is + * up to the callback to take care of sending an appropriate 4xx or 5xx response code. In such + * a case, the callback should return -1. This will tell the function that called the handler + * that an appropriate error response has been sent. If the callback returns 0, however, then + * the caller of the callback will generate a new entity tag and send a 200 OK response. + * + * ESC entries are reference-counted, however as an implementor of a specific event package, + * this should be transparent, since the reference counts are handled by the general ESC + * framework. + * + * EPA: + * The event publication agent in chan_sip is structured quite a bit differently than the + * ESC. With an ESC, an appropriate entry has to be found based on the contents of an incoming + * PUBLISH message. With an EPA, the application interested in sending the PUBLISH can maintain + * a reference to the appropriate EPA entry instead. Similarly, when matching a PUBLISH response + * to an appropriate EPA entry, the sip_pvt can maintain a reference to the corresponding + * EPA entry. The result of this train of thought is that there is no compelling reason to + * maintain a container of these entries. + * + * Instead, there is only the sip_epa_entry structure. Every sip_epa_entry has an entity tag + * that it maintains so that subsequent PUBLISH requests will be identifiable by the ESC on + * the far end. In addition, there is a static_data field which contains information that is + * common to all sip_epa_entries for a specific event package. This static data includes the + * name of the event package and callbacks for handling specific responses for outgoing PUBLISHes. + * Also, there is a field for pointing to instance-specific data. This can include the current + * published state or other identifying information that is specific to an instance of an EPA + * entry of a particular event package. + * + * When an application wishes to send a PUBLISH request, it simply will call create_epa_entry, + * followed by transmit_publish in order to send the PUBLISH. That's all that is necessary. + * Like with ESC entries, sip_epa_entries are reference counted. Unlike ESC entries, though, + * sip_epa_entries reference counts have to be maintained to some degree by the application making + * use of the sip_epa_entry. The application will acquire a reference to the EPA entry when it + * calls create_epa_entry. When the application has finished using the EPA entry (which may not + * be until after several PUBLISH transactions have taken place) it must use ao2_ref to decrease + * the reference count by 1. + */ + +/*! + * \brief The states that can be represented in a SIP call-completion PUBLISH + */ +enum sip_cc_publish_state { + /*! Closed, i.e. unavailable */ + CC_CLOSED, + /*! Open, i.e. available */ + CC_OPEN, +}; + +/*! + * \brief The states that can be represented in a SIP call-completion NOTIFY + */ +enum sip_cc_notify_state { + /*! Queued, i.e. unavailable */ + CC_QUEUED, + /*! Ready, i.e. available */ + CC_READY, +}; + +/*! + * \brief The types of PUBLISH messages defined in RFC 3903 + */ +enum sip_publish_type { + /*! + * \brief Unknown + * + * \details + * This actually is not defined in RFC 3903. We use this as a constant + * to indicate that an incoming PUBLISH does not fit into any of the + * other categories and is thus invalid. + */ + SIP_PUBLISH_UNKNOWN, + /*! + * \brief Initial + * + * \details + * The first PUBLISH sent. This will contain a non-zero Expires header + * as well as a body that indicates the current state of the endpoint + * that has sent the message. The initial PUBLISH is the only type + * of PUBLISH to not contain a Sip-If-Match header in it. + */ + SIP_PUBLISH_INITIAL, + /*! + * \brief Refresh + * + * \details + * Used to keep a published state from expiring. This will contain a + * non-zero Expires header but no body since its purpose is not to + * update state. + */ + SIP_PUBLISH_REFRESH, + /*! + * \brief Modify + * + * \details + * Used to change state from its previous value. This will contain + * a body updating the published state. May or may not contain an + * Expires header. + */ + SIP_PUBLISH_MODIFY, + /*! + * \brief Remove + * + * \details + * Used to remove published state from an ESC. This will contain + * an Expires header set to 0 and likely no body. + */ + SIP_PUBLISH_REMOVE, +}; + +/*! + * Data which is the same for all instances of an EPA for a + * particular event package + */ +struct epa_static_data { + /*! The event type */ + enum subscriptiontype event; + /*! + * The name of the event as it would + * appear in a SIP message + */ + const char *name; + /*! + * The callback called when a 200 OK is received on an outbound PUBLISH + */ + void (*handle_ok)(struct sip_pvt *, struct sip_request *, struct sip_epa_entry *); + /*! + * The callback called when an error response is received on an outbound PUBLISH + */ + void (*handle_error)(struct sip_pvt *, const int resp, struct sip_request *, struct sip_epa_entry *); + /*! + * Destructor to call to clean up instance data + */ + void (*destructor)(void *instance_data); +}; + +/*! + * \brief backend for an event publication agent + */ +struct epa_backend { + const struct epa_static_data *static_data; + AST_LIST_ENTRY(epa_backend) next; +}; + +struct sip_epa_entry { + /*! + * When we are going to send a publish, we need to + * know the type of PUBLISH to send. + */ + enum sip_publish_type publish_type; + /*! + * When we send a PUBLISH, we have to be + * sure to include the entity tag that we + * received in the previous response. + */ + char entity_tag[SIPBUFSIZE]; + /*! + * The destination to which this EPA should send + * PUBLISHes. This may be the name of a SIP peer + * or a hostname. + */ + char destination[SIPBUFSIZE]; + /*! + * The body of the most recently-sent PUBLISH message. + * This is useful for situations such as authentication, + * in which we must send a message identical to the + * one previously sent + */ + char body[SIPBUFSIZE]; + /*! + * Every event package has some constant data and + * callbacks that all instances will share. This + * data resides in this field. + */ + const struct epa_static_data *static_data; + /*! + * In addition to the static data that all instances + * of sip_epa_entry will have, each instance will + * require its own instance-specific data. + */ + void *instance_data; +}; + +/*! + * \brief Instance data for a Call completion EPA entry + */ +struct cc_epa_entry { + /*! + * The core ID of the CC transaction + * for which this EPA entry belongs. This + * essentially acts as a unique identifier + * for the entry and is used in the hash + * and comparison functions + */ + int core_id; + /*! + * We keep the last known state of the + * device in question handy in case + * it needs to be known by a third party. + * Also, in the case where for some reason + * we get asked to transmit state that we + * already sent, we can just ignore the + * request. + */ + enum sip_cc_publish_state current_state; +}; + +struct event_state_compositor; + +/*! + * \brief common ESC items for all event types + * + * The entity_id field serves as a means by which + * A specific entry may be found. + */ +struct sip_esc_entry { + /*! + * The name of the party who + * sent us the PUBLISH. This will more + * than likely correspond to a peer name. + * + * This field's utility isn't really that + * great. It's mainly just a user-recognizable + * handle that can be printed in debug messages. + */ + const char *device_name; + /*! + * The event package for which this esc_entry + * exists. Most of the time this isn't really + * necessary since you'll have easy access to the + * ESC which contains this entry. However, in + * some circumstances, we won't have the ESC + * available. + */ + const char *event; + /*! + * The entity ID used when corresponding + * with the EPA on the other side. As the + * ESC, we generate an entity ID for each + * received PUBLISH and store it in this + * structure. + */ + char entity_tag[30]; + /*! + * The ID for the scheduler. We schedule + * destruction of a sip_esc_entry when we + * receive a PUBLISH. The destruction is + * scheduled for the duration received in + * the Expires header. + */ + int sched_id; + /*! + * Each ESC entry will be for a specific + * event type. Those entries will need to + * carry data which is intrinsic to the + * ESC entry but which is specific to + * the event package + */ + void *event_specific_data; +}; + +typedef int (* const esc_publish_callback)(struct sip_pvt *, struct sip_request *, struct event_state_compositor *, struct sip_esc_entry *); + +/*! + * \brief Callbacks for SIP ESCs + * + * \details + * The names of the callbacks are self-explanatory. The + * corresponding handler is called whenever the specific + * type of PUBLISH is received. + */ +struct sip_esc_publish_callbacks { + const esc_publish_callback initial_handler; + const esc_publish_callback refresh_handler; + const esc_publish_callback modify_handler; + const esc_publish_callback remove_handler; +}; + +struct sip_cc_agent_pvt { + int offer_timer_id; + /* A copy of the original call's Call-ID. + * We use this as a search key when attempting + * to find a particular sip_pvt. + */ + char original_callid[SIPBUFSIZE]; + /* A copy of the exten called originally. + * We use this to set the proper extension + * to dial during the recall since the incoming + * request URI is one that was generated just + * for the recall + */ + char original_exten[SIPBUFSIZE]; + /* A reference to the dialog which we will + * be sending a NOTIFY on when it comes time + * to send one + */ + struct sip_pvt *subscribe_pvt; + /* When we send a NOTIFY, we include a URI + * that should be used by the caller when he + * wishes to send a PUBLISH or INVITE to us. + * We store that URI here. + */ + char notify_uri[SIPBUFSIZE]; + /* When we advertise call completion to a caller, + * we provide a URI for the caller to use when + * he sends us a SUBSCRIBE. We store it for matching + * purposes when we receive the SUBSCRIBE from the + * caller. + */ + char subscribe_uri[SIPBUFSIZE]; + char is_available; +}; + +struct sip_monitor_instance { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(subscribe_uri); + AST_STRING_FIELD(notify_uri); + AST_STRING_FIELD(peername); + AST_STRING_FIELD(device_name); + ); + int core_id; + struct sip_pvt *subscription_pvt; + struct sip_epa_entry *suspension_entry; +}; + #endif diff --git a/configs/ccss.conf.sample b/configs/ccss.conf.sample new file mode 100644 index 000000000..420e4367b --- /dev/null +++ b/configs/ccss.conf.sample @@ -0,0 +1,150 @@ +[general] +; There is only a single option that may be defined in this file. +; The cc_max_requests option is a global limit on the number of +; CC requests that may be in the Asterisk system at any time. +; +;cc_max_requests = 20 +; +; +;============================================ +; PLEASE READ THIS!!! +; The options described below should NOT be +; set in this file. Rather, they should be +; set per-device in a channel driver +; configuration file. +; PLEASE READ THIS!!! +;=========================================== +; +;--------------------------------------------------------------------- +; Timers +;--------------------------------------------------------------------- +;There are three configurable timers for all types of CC: the +;cc_offer_timer, the ccbs_available_timer, and the ccnr_available_timer. +;In addition, when using a generic agent, there is a fourth timer, +;the cc_recall_timer. All timers are configured in seconds, and the +;values shown below are the defaults. +; +;When a caller is offered CCBS or CCNR, the cc_offer_timer will +;be started. If the caller does not request CC before the +;cc_offer_timer expires, then the caller will be unable to request +;CC for this call. +; +;cc_offer_timer = 20 +; +;Once a caller has requested CC, then either the ccbs_available_timer +;or the ccnr_available_timer will run, depending on the service +;requested. The reason why there are two separate timers for CCBS +;and CCNR is that it is reasonable to want to have a shorter timeout +;configured for CCBS than for CCNR. If the available timer expires +;before the called party becomes available, then the CC attempt +;will have failed and monitoring of the called party will stop. +; +;ccbs_available_timer = 4800 +;ccnr_available_timer = 7200 +; +; When using a generic agent, the original caller is called back +; when one of the original called parties becomes available. The +; cc_recall_timer tells Asterisk how long it should let the original +; caller's phone ring before giving up. Please note that this parameter +; only affects operation when using a generic agent. +; +;cc_recall_timer = 20 +;--------------------------------------------------------------------- +; Policies +;--------------------------------------------------------------------- +; Policy settings tell Asterisk how to behave and what sort of +; resources to allocate in order to facilitate CC. There are two +; settings to control the actions Asterisk will take. +; +; The cc_agent_policy describes the behavior that Asterisk will +; take when communicating with the caller during CC. There are +; three possible options. +; +;never: Never offer CC to the caller. Setting the cc_agent_policy +; to this value is the way to disable CC for a call. +; +;generic: A generic CC agent is one which uses no protocol-specific +; mechanisms to offer CC to the caller. Instead, the caller +; requests CC using a dialplan function. Due to internal +; restrictions, you should only use a generic CC agent on +; phones (i.e. not "trunks"). If you are using phones which +; do not support a protocol-specific method of using CC, then +; generic CC agents are what you should use. +; +;native: A native CC agent is one which uses protocol-specific +; signaling to offer CC to the caller and accept CC requests +; from the caller. The supported protocols for native CC +; agents are SIP, ISDN ETSI PTP, ISDN ETSI PTMP, and Q.SIG +;cc_agent_policy=never +; +; The cc_monitor_policy describes the behavior that Asterisk will +; take when communicating with the called party during CC. There +; are four possible options. +; +;never: Analogous to the cc_agent_policy setting. We will never +; attempt to request CC services on this interface. +; +;generic: Analogous to the cc_agent_policy setting. We will monitor +; the called party's progress using protocol-agnostic +; capabilities. Like with generic CC agents, generic CC +; monitors should only be used for phones. +; +;native: Analogous to the cc_agent_policy setting. We will use +; protocol-specific methods to request CC from this interface +; and to monitor the interface for availability. +; +;accept: If an interface is set to "accept," then we will accept +; protocol-specific CC offers from the caller and use +; a native CC monitor for the remainder of the CC transaction. +; However, if the interface does not offer protocol-specific +; CC, then we will fall back to using a generic CC monitor +; instead. This is a good setting to use for phones for which +; you do not know if they support protocol-specific CC +; methodologies. +;cc_monitor_policy=never +; +; +;--------------------------------------------------------------------- +; Limits +;--------------------------------------------------------------------- +; +; The use of CC requires Asterisk to potentially use more memory than +; some administrators would like. As such, it is a good idea to limit +; the number of CC requests that can be in the system at a given time. +; The values shown below are the defaults. +; +; The cc_max_agents setting limits the number of outstanding CC +; requests a caller may have at any given time. Please note that due +; to implementation restrictions, this setting is ignored when using +; generic CC agents. Generic CC agents may only have one outstanding +; CC request. +; +;cc_max_agents = 5 +; +; The cc_max_monitors setting limits the number of outstanding CC +; requests can be made to a specific interface at a given time. +; +;cc_max_monitors = 5 +; +;--------------------------------------------------------------------- +; Other +;--------------------------------------------------------------------- +; +; When using a generic CC agent, the caller who requested CC will be +; called back when a called party becomes available. When the caller +; answers his phone, the administrator may opt to have a macro run. +; What this macro does is up to the administrator. By default there +; is no callback macro configured. +; +;cc_callback_macro= +; +; When using an ISDN phone and a generic CC agent, Asterisk is unable +; to determine the dialstring that should be used when calling back +; the original caller. Furthermore, if you desire to use any dialstring- +; specific options, such as distinctive ring, you must set this +; configuration option. For non-ISDN phones, it is not necessary to +; set this, since Asterisk can determine the dialstring to use since +; it is identical to the name of the calling device. By default, there +; is no cc_agent_dialstring set. +; +;cc_agent_dialstring= diff --git a/configs/chan_dahdi.conf.sample b/configs/chan_dahdi.conf.sample index fb0d06931..30229b5fc 100644 --- a/configs/chan_dahdi.conf.sample +++ b/configs/chan_dahdi.conf.sample @@ -85,10 +85,11 @@ ;service_message_support=yes ; Enable service message support for channel. Must be set after switchtype. ; -; PRI Reverse Charging Indication: Indicate to the called party that the -; call will be reverse charged. To enable, prefix the dialed number with one -; of the following letters: -; C - Reverse Charge Indication Requested +; Dialing options for ISDN (i.e., Dial(DAHDI/g1/exten/options)): +; R Reverse Charge Indication +; Indicate to the called party that the call will be reverse charged. +; K(n) Keypad digits n +; Send out the specified digits as keypad digits. ; ; PRI Dialplan: The ISDN-level Type Of Number (TON) or numbering plan, used for ; the dialed number. For most installations, leaving this as 'unknown' (the @@ -236,9 +237,52 @@ ; May vary in other ISDN standards (Q.931 1993 : 90000 ms) ; T313: Wait for CONNECT acknowledge, CPE side only (default 3000 ms) ; +; T-RESPONSE: Maximum time to wait for a typical APDU response. (default 4000 ms) +; This is an implementation timer when the standard does not specify one. +; T-ACTIVATE: Request supervision timeout. (default 10000 ms) +; T-RETENTION: Maximum time to wait for user A to activate call-completion. (default 30000 ms) +; Used by ETSI PTP, ETSI PTMP, and Q.SIG as the cc_offer_timer. +; T-CCBS1: T-STATUS timer equivalent for CC user A status. (default 4000 ms) +; T-CCBS2: Maximum time the CCBS service will be active (default 45 min in ms) +; T-CCBS3: Maximum time to wait for user A to respond to user B availability. (default 20000 ms) +; T-CCBS5: Network B CCBS supervision timeout. (default 60 min in ms) +; T-CCBS6: Network A CCBS supervision timeout. (default 60 min in ms) +; T-CCNR2: Maximum time the CCNR service will be active (default 180 min in ms) +; T-CCNR5: Network B CCNR supervision timeout. (default 195 min in ms) +; T-CCNR6: Network A CCNR supervision timeout. (default 195 min in ms) +; CC-T1: Q.SIG CC request supervision timeout. (default 30000 ms) +; CCBS-T2: Q.SIG CCBS supervision timeout. (default 60 min in ms) +; CCNR-T2: Q.SIG CCNR supervision timeout. (default 195 min in ms) +; CC-T3: Q.SIG CC Maximum time to wait for user A to respond to user B availability. (default 30000 ms) +; ;pritimer => t200,1000 ;pritimer => t313,4000 ; +; CC PTMP recall mode: +; specific - Only the CC original party A can participate in the CC callback +; global - Other compatible endpoints on the PTMP line can be party A in the CC callback +; +; cc_ptmp_recall_mode cannot be changed on a reload. +; +;cc_ptmp_recall_mode = specific +; +; CC Q.SIG Party A (requester) retain signaling link option +; retain Require that the signaling link be retained. +; release Request that the signaling link be released. +; do_not_care The responder is free to choose if the signaling link will be retained. +; +;cc_qsig_signaling_link_req = retain +; +; CC Q.SIG Party B (responder) retain signaling link option +; retain Prefer that the signaling link be retained. +; release Prefer that the signaling link be released. +; +;cc_qsig_signaling_link_rsp = retain +; +; See ccss.conf.sample for more options. The timers described by ccss.conf.sample +; are not used by ISDN for the native protocol since they are defined by the +; standards and set by pritimer above. +; ; To enable transmission of facility-based ISDN supplementary services (such ; as caller name from CPE over facility), enable this option. ; Cannot be changed on a reload. @@ -267,6 +311,10 @@ ; fxo_ks: FXO (Kewl Start) ; pri_cpe: PRI signalling, CPE side ; pri_net: PRI signalling, Network side +; bri_cpe: BRI PTP signalling, CPE side +; bri_net: BRI PTP signalling, Network side +; bri_cpe_ptmp: BRI PTMP signalling, CPE side +; bri_net_ptmp: BRI PTMP signalling, Network side ; sf: SF (Inband Tone) Signalling ; sf_w: SF Wink ; sf_featd: SF Feature Group D (The fake, Adtran style, DTMF) diff --git a/configs/manager.conf.sample b/configs/manager.conf.sample index 229db2dac..078d17932 100644 --- a/configs/manager.conf.sample +++ b/configs/manager.conf.sample @@ -84,6 +84,7 @@ bindaddr = 0.0.0.0 ; Write authorization permits you to send commands and get back responses. The ; following classes exist: ; +; all - All event classes below (including any we may have missed). ; system - General information about the system and ability to run system ; management commands, such as Shutdown, Restart, and Reload. ; call - Information about channels and ability to set information in a @@ -100,6 +101,8 @@ bindaddr = 0.0.0.0 ; cdr - Output of cdr_manager, if loaded. Read-only. ; dialplan - Receive NewExten and VarSet events. Read-only. ; originate - Permission to originate new calls. Write-only. +; agi - Output AGI commands executed. Input AGI command to execute. +; cc - Call Completion events. Read-only. ; ;read = system,call,log,verbose,agent,user,config,dtmf,reporting,cdr,dialplan ;write = system,call,agent,user,config,command,reporting,originate diff --git a/configure.ac b/configure.ac index 7a4cbb8eb..9e774683b 100644 --- a/configure.ac +++ b/configure.ac @@ -335,6 +335,7 @@ AST_EXT_LIB_SETUP([PGSQL], [PostgreSQL], [postgres]) AST_EXT_LIB_SETUP([POPT], [popt], [popt]) AST_EXT_LIB_SETUP([PORTAUDIO], [PortAudio], [portaudio]) AST_EXT_LIB_SETUP([PRI], [ISDN PRI], [pri]) +AST_EXT_LIB_SETUP_DEPENDENT([PRI_CCSS], [ISDN PRI call completion supplementary service], [PRI], [pri]) AST_EXT_LIB_SETUP_DEPENDENT([PRI_SUBADDR], [ISDN PRI subaddressing], [PRI], [pri]) AST_EXT_LIB_SETUP_DEPENDENT([PRI_CALL_HOLD], [ISDN PRI call hold], [PRI], [pri]) AST_EXT_LIB_SETUP_DEPENDENT([PRI_CALL_REROUTING], [ISDN PRI call rerouting and call deflection], [PRI], [pri]) @@ -1543,6 +1544,7 @@ AST_EXT_LIB_CHECK([POPT], [popt], [poptStrerror], [popt.h]) AST_EXT_LIB_CHECK([PORTAUDIO], [portaudio], [Pa_GetDeviceCount], [portaudio.h]) AST_EXT_LIB_CHECK([PRI], [pri], [pri_connected_line_update], [libpri.h]) +AST_EXT_LIB_CHECK([PRI_CCSS], [pri], [pri_cc_enable], [libpri.h]) AST_EXT_LIB_CHECK([PRI_SUBADDR], [pri], [pri_sr_set_called_subaddress], [libpri.h]) AST_EXT_LIB_CHECK([PRI_CALL_HOLD], [pri], [pri_hold_enable], [libpri.h]) AST_EXT_LIB_CHECK([PRI_CALL_REROUTING], [pri], [pri_reroute_enable], [libpri.h]) diff --git a/doc/tex/asterisk.tex b/doc/tex/asterisk.tex index 8097d14ed..0427389b1 100644 --- a/doc/tex/asterisk.tex +++ b/doc/tex/asterisk.tex @@ -147,6 +147,9 @@ reference purposes. \chapter{Security Framework} \input{security-events.tex} +\chapter{Call Completion Supplementary Services} + \input{ccss.tex} + \chapter{Development} \section{Backtrace} \input{backtrace.tex} diff --git a/doc/tex/ccss.tex b/doc/tex/ccss.tex new file mode 100644 index 000000000..cfe07cbe0 --- /dev/null +++ b/doc/tex/ccss.tex @@ -0,0 +1,414 @@ +\section{Introduction} + + A new feature for Asterisk 1.8 is Call Completion Supplementary +Services. This document aims to explain the system and how to use it. +In addition, this document examines some potential troublesome points +which administrators may come across during their deployment of the +feature. + +\section{What is CCSS?} + + Call Completion Supplementary Services (often abbreviated "CCSS" or +simply "CC") allow for a caller to let Asterisk automatically alert him +when a called party has become available, given that a previous call to +that party failed for some reason. The two services offered are Call +Completion on Busy Subscriber (CCBS) and Call Completion on No Response +(CCNR). + To illustrate, let's say that Alice attempts to call Bob. Bob is +currently on a phone call with Carol, though, so Alice hears a busy +signal. In this situation, assuming that Asterisk has been configured +to allow for such activity, Alice would be able to request CCBS. Once +Bob has finished his phone call, Alice will be alerted. Alice can then +attempt to call Bob again. + +\section{Glossary of Terms} + + In this document, we will use some terms which may require +clarification. Most of these terms are specific to Asterisk, and are by +no means standard. + +\begin{itemize} +\item CCBS: Call Completion on Busy Subscriber. When a call fails because the +recipient's phone is busy, the caller will have the opportunity to +request CCBS. When the recipient's phone is no longer busy, the caller +will be alerted. The means by which the caller is alerted is dependent +upon the type of agent used by the caller. + +\item CCNR: Call Completion on No Response. When a call fails because the +recipient does not answer the phone, the caller will have the opportun- +ity to request CCNR. When the recipient's phone becomes busy and then +is no longer busy, the caller will be alerted. The means by which the +caller is alerted is dependent upon the type of the agent used by the +caller. + +\item Agent: The agent is the entity within Asterisk that communicates with +and acts on behalf of the calling party. + +\item Monitor: The monitor is the entity within Asterisk that communicates +with and monitors the status of the called party. + +\item Generic Agent: A generic agent is an agent that uses protocol-agnostic +methods to communicate with the caller. Generic agents should only be +used for phones, and never should be used for "trunks." + +\item Generic Monitor: A generic monitor is a monitor that uses protocol- +agnostic methods to monitor the status of the called party. Like with +generic agents, generic monitors should only be used for phones. + +\item Native Agent: The opposite of a generic agent. A native agent uses +protocol-specific messages to communicate with the calling party. +Native agents may be used for both phones and trunks, but it must be +known ahead of time that the device with which Asterisk is communica- +ting supports the necessary signaling. + +\item Native Monitor: The opposite of a generic monitor. A native monitor +uses protocol-specific messages to subscribe to and receive notifica- +tion of the status of the called party. Native monitors may be used +for both phones and trunks, but it must be known ahead of time that +the device with which Asterisk is communicating supports the +necessary signaling. + +\item Offer: An offer of CC refers to the notification received by the caller +that he may request CC. + +\item Request: When the caller decides that he would like to subscribe to CC, +he will make a request for CC. Furthermore, the term may refer to any +outstanding requests made by callers. + +\item Recall: When the caller attempts to call the recipient after being +alerted that the recipient is available, this action is referred to +as a "recall." +\end{itemize} + +\section{The CC Process} + +\subsection{The Initial Call} + + The only requirement for the use of CC is to configure an agent for +the caller and a monitor for at least one recipient of the call. +This is controlled using the cc\_agent\_policy for the caller and the +cc\_monitor\_policy for the recipient. For more information about these +configuration settings, see configs/samples/ccss.conf.sample. If the +agent for the caller is set to something other than "never" and at +least one recipient has his monitor set to something other than +"never," then CC will be offered to the caller at the end of the +call. + + Once the initial call has been hung up, the configured +cc\_offer\_timer for the caller will be started. If the caller wishes to +request CC for the previous call, he must do so before the timer +expires. + +\subsection{Requesting CC} + + Requesting CC is done differently depending on the type of agent +the caller is using. + + With generic agents, the CallCompletionRequest application must be +called in order to request CC. There are two different ways in which +this may be called. It may either be called before the caller hangs up +during the initial call, or the caller may hang up from the initial +call and dial an extension which calls the CallCompletionRequest +application. If the second method is used, then the caller will +have until the cc\_offer\_timer expires to request CC. + + With native agents, the method for requesting CC is dependent upon +the technology being used, coupled with the make of equipment. It may +be possible to request CC using a programmable key on a phone or by +clicking a button on a console. If you are using equipment which can +natively support CC but do not know the means by which to request it, +then contact the equipment manufacturer for more information. + +\subsection{Cancelling CC} + + CC may be canceled after it has been requested. The method by which +this is accomplished differs based on the type of agent the calling +party uses. + + When using a generic agent, the dialplan application +CallRequestCancel is used to cancel CC. When using a native monitor, +the method by which CC is cancelled depends on the protocol used. +Likely, this will be done using a button on a phone. + + Keep in mind that if CC is cancelled, it cannot be un-cancelled. + +\subsection{Monitoring the Called Party} + + Once the caller has requested CC, then Asterisk's job is to monitor +the progress of the called parties. It is at this point that Asterisk +allocates the necessary resources to monitor the called parties. + + A generic monitor uses Asterisk's device state subsystem in order +to determine when the called party has become available. For both CCBS +and CCNR, Asterisk simply waits for the phone's state to change to +a "not in use" state from a different state. Once this happens, then +Asterisk will consider the called party to be available and will alert +the caller. + + A native monitor relies on the network to send a protocol-specific +message when the called party has become available. When Asterisk +receives such a message, it will consider the called party to be +available and will alert the caller. + + Note that since a single caller may dial multiple parties, a monitor +is used for each called party. It is within reason that different called +parties will use different types of monitors for the same CC request. + +\subsection{Alerting the Caller} + + Once Asterisk has determined that the called party has become available +the time comes for Asterisk to alert the caller that the called party has +become available. The method by which this is done differs based on the +type of agent in use. + + If a generic agent is used, then Asterisk will originate a call to +the calling party. Upon answering the call, if a callback macro has +been configured, then that macro will be executed on the calling +party's channel. After the macro has completed, an outbound call +will be issued to the parties involved in the original call. + + If a native agent is used, then Asterisk will send an appropriate +notification message to the calling party to alert it that it may now +attempt its recall. How this is presented to the caller is dependent +upon the protocol and equipment that the caller is using. It is +possible that the calling party's phone will ring and a recall will +be triggered upon answering the phone, or it may be that the user +has a specific button that he may press to initiate a recall. + +\subsection{If the Caller is unavailable} + + When the called party has become available, it is possible that +when Asterisk attempts to alert the calling party of the called party's +availability, the calling party itself will have become unavailable. +If this is the case, then Asterisk will suspend monitoring of the +called party and will instead monitor the availability of the calling +party. The monitoring procedure for the calling party is the same +as is used in the section "Monitoring the Called Party." In other +words, the method by which the calling party is monitored is dependent +upon the type of agent used by the caller. + + Once Asterisk has determined that the calling party has become +available again, Asterisk will then move back to the process used +in the section "Monitoring the Called Party." + +\subsection{The CC recall} + + The calling party will make its recall to the same extension +that was dialed. Asterisk will provide a channel variable, +CC\_INTERFACES, to be used as an argument to the Dial application +for CC recalls. It is strongly recommended that you use this +channel variable during a CC recall. Listed are two reasons: + +\begin{itemize} +\item The dialplan may be written in such a way that the dialed +destintations are dynamically generated. With such a dialplan, it +cannot be guaranteed that the same interfaces will be recalled. +\item For calling destinations with native CC monitors, it may be +necessary to dial a special string in order to notify the channel +driver that the number being dialed is actually part of a CC recall. +\end{itemize} + + Note that even if your call gets routed through local channels, +the CC\_INTERFACES variable will be populated with the appropriate +values for that specific extension. + When the called parties are dialed, it is expected that a called +party will answer, since Asterisk had previously determined that the +party was available. However, it is possible that the called party +may choose not to respond to the call, or he could have become busy +again. In such a situation, the calling party must re-request CC if +he wishes to still be alerted when the calling party has become +available. + +\section{Miscellaneous Information and Tips} + +\begin{itemize} +\item Be aware when using a generic agent that the max\_cc\_agents +configuration parameter is ignored. The main driving reason for +this is that the mechanism for cancelling CC when using a generic +agent would become much more potentially confusing to execute. By +limiting a calling party to having a single request, there is only +ever a single request to be cancelled, making the process simple. + +\item Keep in mind that no matter what CC agent type is being used, +a CC request can only be made for the latest call issued. + +\item If available timers are running on multiple called parties, +it is possible that one of the timers may expire before the others +do. If such a situation occurs, then the interface on which the +timer expired will cease to be monitored. If, though, one of the +other called parties becomes available before his available timer +expires, the called party whose available timer had previously +expired will still be included in the CC\_INTERFACES channel +variable on the recall. + +\item It is strongly recommended that lots of thought is placed +into the settings of the CC timers. Our general recommendation is +that timers for phones should be set shorter than those for trunks. +The reason for this is that it makes it less likely for a link in +the middle of a network to cause CC to fail. + +\item CC can potentially be a memory hog if used irresponsibly. The +following are recommendations to help curb the amount of resources +required by the CC engine. First, limit the maximum number of +CC requests in the system using the cc\_max\_requests option in +ccss.conf. Second, set the cc\_offer\_timer low for your callers. Since +it is likely that most calls will not result in a CC request, it is +a good idea to set this value to something low so that information +for calls does not stick around in memory for long. The final thing +that can be done is to conditionally set the cc\_agent\_policy to +"never" using the CALLCOMPLETION dialplan function. By doing this, +no CC information will be kept around after the call completes. + +\item It is possible to request CCNR on answered calls. The reason +for this is that it is impossible to know whether a call that is +answered has actually been answered by a person or by something +such as voicemail or some other IVR. + +\item Not all channel drivers have had the ability to set CC config +parameters in their configuration files added yet. At the time of +this writing (2009 Oct), only chan\_sip has had this ability added, with +short-term plans to add this to chan\_dahdi as well. It is +possible to set CC configuration parameters for other channel types, +though. For these channel types, the setting of the parameters can +only be accomplished using the CALLCOMPLETION dialplan function. + +\item It is documented in many places that generic agents and monitors +can only be used for phones. In most cases, however, Asterisk has no +way of distinguishing between a phone and a trunk itself. The result +is that Asterisk will happily let you violate the advice given and +allow you to set up a trunk with a generic monitor or agent. While this +will not cause anything catastrophic to occur, the behavior will most +definitely not be what you want. + +\item At the time of this writing (2009 Oct), Asterisk is the only +known SIP stack to write an implementation of +draft-ietf-bliss-call-completion-04. As a result, it is recommended +that for your SIP phones, use a generic agent and monitor. For SIP +trunks, you will only be able to use CC if the other end is +terminated by another Asterisk server running version 1.8 or later. + +\item If the Dial application is called multiple times by a single +extension, CC will only be offered to the caller for the parties called +by the first instantiation of Dial. + +\item If a phone forwards a call, then CC may only be requested for +the phone that executed the call forward. CC may not be requested +for the phone to which the call was forwarded. + +\item CC is currently only supported by the Dial application. Queue, +Followme, and Page do not support CC because it is not particularly +useful for those applications. + +\item Generic CC relies heavily on accurate device state reporting. In +particular, when using SIP phones it is vital to be sure that device +state is updated properly when using them. In order to facilitate proper +device state handling, be sure to set callcounter=yes for all peers and +to set limitonpeers=yes in the general section of sip.conf + +\item When using SIP CC (i.e. native CC over SIP), it is important that +your minexpiry and maxexpiry values allow for available timers to run +as little or as long as they are configured. When an Asterisk server +requests call completion over SIP, it sends a SUBSCRIBE message with +an Expires header set to the number of seconds that the available +timer should run. If the Asterisk server that receives this SUBSCRIBE +has a maxexpiry set lower than what is in the received Expires header, +then the available timer will only run for maxexpiry seconds. + +\item As with all Asterisk components, CC is not perfect. If you should +find a bug or wish to enhance the feature, please open an issue on +https://issues.asterisk.org. If writing an enhancement, please be sure +to include a patch for the enhancement, or else the issue will be +closed. + +\end{itemize} + +\section{Simple Example of generic call completion} + +The following is an incredibly bare-bones example sip.conf +and dialplan to show basic usage of generic call completion. +It is likely that if you have a more complex setup, you will +need to make use of items like the CALLCOMPLETION dialplan +function or the CC\_INTERFACES channel variable. + +First, let's establish a very simple sip.conf to use for this + +\begin{verbatim} +[Mark] +context=phone_calls +cc_agent_policy=generic +cc_monitor_policy=generic +;We will accept defaults for the rest of the cc parameters +;We also are not concerned with other SIP details for this +;example + +[Richard] +context=phone_calls +cc_agent_policy=generic +cc_monitor_policy=generic +\end{verbatim} + +Now, let's write a simple dialplan + +\begin{verbatim} +[phone_calls] + +exten => 1000,1,Dial(SIP/Mark,20) +exten => 1000,n,Hangup + +exten => 2000,1,Dial(SIP/Richard,20) +exten => 2000,n,Hangup + +exten => 30,1,CallCompletionRequest +exten => 30,n,Hangup + +exten => 31,1,CallCompletionCancel +exten => 31,n,Hangup +\end{verbatim} + +\begin{itemize} +\item Scenario 1: +Mark picks up his phone and dials Richard by dialing 2000. Richard is +currently on a call, so Mark hears a busy signal. Mark then hangs up, +picks up the phone and dials 30 to call the CallCompletionRequest +application. After some time, Richard finishes his call and hangs up. +Mark is automatically called back by Asterisk. When Mark picks up his +phone, Asterisk will dial extension 2000 for him. + +\item Scenario 2: +Richard picks up his phone and dials Mark by dialing 1000. Mark has stepped +away from his desk, and so he is unable to answer the phone within the +20 second dial timeout. Richard hangs up, picks the phone back up and then +dials 30 to request call completion. Mark gets back to his desk and dials +somebody's number. When Mark finishes the call, Asterisk detects that Mark's +phone has had some activity and has become available again and rings Richard's +phone. Once Richard picks up, Asterisk automatically dials exteision 1000 for +him. + +\item Scenario 3: +Much like scenario 1, Mark calls Richard and Richard is busy. Mark hangs up, +picks the phone back up and then dials 30 to request call completion. After +a little while, Mark realizes he doesn't actually need to talk to Richard, so +he dials 31 to cancel call completion. When Richard becomes free, Mark will +not automatically be redialed by Asterisk. + +\item Scenario 4: +Richard calls Mark, but Mark is busy. About thirty seconds later, Richard decides +that he should perhaps request call completion. However, since Richard's phone +has the default cc\_offer\_timer of 20 seconds, he has run out of time to +request call completion. He instead must attempt to dial Mark again manually. If +Mark is still busy, Richard can attempt to request call completion on this second +call instead. + +\item Scenario 5: +Mark calls Richard, and Richard is busy. Mark requests call completion. Richard +does not finish his current call for another 2 hours (7200 seconds). Since Mark +has the default ccbs\_available\_timer of 4800 seconds set, Mark will not be +automatically recalled by Asterisk when Richard finishes his call. + +\item Scenario 6: +Mark calls Richard, and Richard does not respond within the 20 second dial timeout. +Mark requests call completion. Richard does not use his phone again for another +4 hours (144000 seconds). Since Mark has the default ccnr\_available\_timer +of 7200 seconds set, Mark will not be automatically recalled by Asterisk when +Richard finishes his call. +\end{itemize} diff --git a/funcs/func_callcompletion.c b/funcs/func_callcompletion.c new file mode 100644 index 000000000..191667bb0 --- /dev/null +++ b/funcs/func_callcompletion.c @@ -0,0 +1,114 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2010, Digium, Inc. + * + * Mark Michelson + * + * 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 + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "asterisk/channel.h" +#include "asterisk/ccss.h" +#include "asterisk/pbx.h" + +/*** DOCUMENTATION + + + Get or set a call completion configuration parameter for a channel. + + + + The allowable options are: + + + + + + + + + + + + + + + + The CALLCOMPLETION function can be used to get or set a call + completion configuration parameter for a channel. Note that setting + a configuration parameter will only change the parameter for the + duration of the call. + + + ***/ + +static int acf_cc_read(struct ast_channel *chan, const char *name, char *data, + char *buf, size_t buf_len) +{ + struct ast_cc_config_params *cc_params; + int res; + + ast_channel_lock(chan); + if (!(cc_params = ast_channel_get_cc_config_params(chan))) { + ast_channel_unlock(chan); + return -1; + } + + res = ast_cc_get_param(cc_params, data, buf, buf_len); + ast_channel_unlock(chan); + return res; +} + +static int acf_cc_write(struct ast_channel *chan, const char *cmd, char *data, + const char *value) +{ + struct ast_cc_config_params *cc_params; + int res; + + ast_channel_lock(chan); + if (!(cc_params = ast_channel_get_cc_config_params(chan))) { + ast_channel_unlock(chan); + return -1; + } + + res = ast_cc_set_param(cc_params, data, value); + ast_channel_unlock(chan); + return res; +} + +static struct ast_custom_function cc_function = { + .name = "CALLCOMPLETION", + .read = acf_cc_read, + .write = acf_cc_write, +}; + +static int unload_module(void) +{ + return ast_custom_function_unregister(&cc_function); +} + +static int load_module(void) +{ + return ast_custom_function_register(&cc_function) == 0 ? AST_MODULE_LOAD_SUCCESS : AST_MODULE_LOAD_DECLINE; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Call Control Configuration Function"); diff --git a/include/asterisk/ccss.h b/include/asterisk/ccss.h new file mode 100644 index 000000000..c2d7ec850 --- /dev/null +++ b/include/asterisk/ccss.h @@ -0,0 +1,1582 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2010, Digium, Inc. + * + * Mark Michelson + * + * 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 API + * \author Mark Michelson + */ + +#ifndef _ASTERISK_CCSS_H +#define _ASTERISK_CCSS_H + +#include "asterisk.h" + +#include "asterisk/linkedlists.h" +#include "asterisk/devicestate.h" + +enum ast_cc_service_type { + /* No Service available/requested */ + AST_CC_NONE, + /* Call Completion Busy Subscriber */ + AST_CC_CCBS, + /* Call Completion No Response */ + AST_CC_CCNR, + /* Call Completion Not Logged In (currently SIP only) */ + AST_CC_CCNL, +}; + +/*! + * \since 1.8 + * \brief The various possibilities for cc_agent_policy values + */ +enum ast_cc_agent_policies { + /*! Never offer CCSS to the caller */ + AST_CC_AGENT_NEVER, + /*! Offer CCSS using native signaling */ + AST_CC_AGENT_NATIVE, + /*! Use generic agent for caller */ + AST_CC_AGENT_GENERIC, +}; + +/*! + * \brief agent flags that can alter core behavior + */ +enum ast_cc_agent_flags { + /* Some agent types allow for a caller to + * request CC without reaching the CC_CALLER_OFFERED + * state. In other words, the caller can request + * CC while he is still on the phone from the failed + * call. The generic agent is an agent which allows + * for this behavior. + */ + AST_CC_AGENT_SKIP_OFFER = (1 << 0), +}; + +/*! + * \since 1.8 + * \brief The various possibilities for cc_monitor_policy values + */ +enum ast_cc_monitor_policies { + /*! Never accept CCSS offers from callee */ + AST_CC_MONITOR_NEVER, + /* CCSS only available if callee offers it through signaling */ + AST_CC_MONITOR_NATIVE, + /*! Always use CCSS generic monitor for callee + * Note that if callee offers CCSS natively, we still + * will use a generic CCSS monitor if this is set + */ + AST_CC_MONITOR_GENERIC, + /*! Accept native CCSS offers, but if no offer is present, + * use a generic CCSS monitor + */ + AST_CC_MONITOR_ALWAYS, +}; + +/* Forward declaration. Struct is in main/ccss.c */ +struct ast_cc_config_params; + +/*! + * \since 1.8 + * \brief Queue an AST_CONTROL_CC frame + * + * \note + * Since this function calls ast_queue_frame, the channel will be + * locked during the course of this function. + * + * \param chan The channel onto which to queue the frame + * \param monitor_type The type of monitor to use when CC is requested + * \param dialstring The dial string used to call the device + * \param service The type of CC service the device is willing to offer + * \param private_data If a native monitor is being used, and some channel-driver-specific private + * data has been allocated, then this parameter should contain a pointer to that data. If using a generic + * monitor, this parameter should remain NULL. Note that if this function should fail at some point, + * it is the responsibility of the caller to free the private data upon return. + * \retval 0 Success + * \retval -1 Error + */ +int ast_queue_cc_frame(struct ast_channel *chan, const char * const monitor_type, + const char * const dialstring, enum ast_cc_service_type service, void *private_data); + +/*! + * \brief Allocate and initialize an ast_cc_config_params structure + * + * \note + * Reasonable default values are chosen for the parameters upon allocation. + * + * \retval NULL Unable to allocate the structure + * \retval non-NULL A pointer to the newly allocated and initialized structure + */ +struct ast_cc_config_params *__ast_cc_config_params_init(const char *file, int line, const char *function); + +/*! + * \brief Allocate and initialize an ast_cc_config_params structure + * + * \note + * Reasonable default values are chosen for the parameters upon allocation. + * + * \retval NULL Unable to allocate the structure + * \retval non-NULL A pointer to the newly allocated and initialized structure + */ +#define ast_cc_config_params_init() __ast_cc_config_params_init(__FILE__, __LINE__, __PRETTY_FUNCTION__) + +/*! + * \brief Free memory from CCSS configuration params + * + * \note + * Just a call to ast_free for now... + * + * \param params Pointer to structure whose memory we need to free + * \retval void + */ +void ast_cc_config_params_destroy(struct ast_cc_config_params *params); + +/*! + * \brief set a CCSS configuration parameter, given its name + * + * \note + * Useful when parsing config files when used in conjunction + * with ast_ccss_is_cc_config_param. + * + * \param params The parameter structure to set the value on + * \param name The name of the cc parameter + * \param value The value of the parameter + * \retval 0 Success + * \retval -1 Failure + */ +int ast_cc_set_param(struct ast_cc_config_params *params, const char * const name, + const char * value); + +/*! + * \brief get a CCSS configuration parameter, given its name + * + * \note + * Useful when reading input as a string, like from dialplan or + * manager. + * + * \param params The CCSS configuration from which to get the value + * \param name The name of the CCSS parameter we want + * \param buf A preallocated buffer to hold the value + * \param buf_len The size of buf + * \retval 0 Success + * \retval -1 Failure + */ +int ast_cc_get_param(struct ast_cc_config_params *params, const char * const name, + char *buf, size_t buf_len); + +/*! + * \since 1.8 + * \brief Is this a CCSS configuration parameter? + * \param name Name of configuration option being parsed. + * \retval 1 Yes, this is a CCSS configuration parameter. + * \retval 0 No, this is not a CCSS configuration parameter. + */ +int ast_cc_is_config_param(const char * const name); + +/*! + * \since 1.8 + * \brief copy CCSS configuration parameters from one structure to another + * + * \details + * For now, this is a simple memcpy, but this function is necessary since + * the size of an ast_cc_config_params structure is unknown outside of + * main/ccss.c. Also, this allows for easier expansion of the function in + * case it becomes more complex than just a memcpy. + * + * \param src The structure from which data is copied + * \param dest The structure to which data is copied + * \retval -1 Copy failed (no way for this to happen yet) + * \retval 0 Copy succeeded + */ +void ast_cc_copy_config_params(struct ast_cc_config_params *dest, const struct ast_cc_config_params *src); + +/*! + * \since 1.8 + * \brief Get the cc_agent_policy + * \param config The configuration to retrieve the policy from + * \return The current cc_agent_policy for this configuration + */ +enum ast_cc_agent_policies ast_get_cc_agent_policy(struct ast_cc_config_params *config); + +/*! + * \since 1.8 + * \brief Set the cc_agent_policy + * \param config The configuration to set the cc_agent_policy on + * \param value The new cc_agent_policy we want to change to + * \retval 0 Success + * \retval -1 Failure (likely due to bad input) + */ +int ast_set_cc_agent_policy(struct ast_cc_config_params *config, enum ast_cc_agent_policies value); + +/*! + * \since 1.8 + * \brief Get the cc_monitor_policy + * \param config The configuration to retrieve the cc_monitor_policy from + * \return The cc_monitor_policy retrieved from the configuration + */ +enum ast_cc_monitor_policies ast_get_cc_monitor_policy(struct ast_cc_config_params *config); + +/*! + * \since 1.8 + * \brief Set the cc_monitor_policy + * \param config The configuration to set the cc_monitor_policy on + * \param value The new cc_monitor_policy we want to change to + * \retval 0 Success + * \retval -1 Failure (likely due to bad input) + */ +int ast_set_cc_monitor_policy(struct ast_cc_config_params *config, enum ast_cc_monitor_policies value); + +/*! + * \since 1.8 + * \brief Get the cc_offer_timer + * \param config The configuration to retrieve the cc_offer_timer from + * \return The cc_offer_timer from this configuration + */ +unsigned int ast_get_cc_offer_timer(struct ast_cc_config_params *config); + +/*! + * \since 1.8 + * \brief Set the cc_offer_timer + * \param config The configuration to set the cc_offer_timer on + * \param value The new cc_offer_timer we want to change to + * \retval void + */ +void ast_set_cc_offer_timer(struct ast_cc_config_params *config, unsigned int value); + +/*! + * \since 1.8 + * \brief Get the ccnr_available_timer + * \param config The configuration to retrieve the ccnr_available_timer from + * \return The ccnr_available_timer from this configuration + */ +unsigned int ast_get_ccnr_available_timer(struct ast_cc_config_params *config); + +/*! + * \since 1.8 + * \brief Set the ccnr_available_timer + * \param config The configuration to set the ccnr_available_timer on + * \param value The new ccnr_available_timer we want to change to + * \retval void + */ +void ast_set_ccnr_available_timer(struct ast_cc_config_params *config, unsigned int value); + +/*! + * \since 1.8 + * \brief Get the cc_recall_timer + * \param config The configuration to retrieve the cc_recall_timer from + * \return The cc_recall_timer from this configuration + */ +unsigned int ast_get_cc_recall_timer(struct ast_cc_config_params *config); + +/*! + * \since 1.8 + * \brief Set the cc_recall_timer + * \param config The configuration to set the cc_recall_timer on + * \param value The new cc_recall_timer we want to change to + * \retval void + */ +void ast_set_cc_recall_timer(struct ast_cc_config_params *config, unsigned int value); + +/*! + * \since 1.8 + * \brief Get the ccbs_available_timer + * \param config The configuration to retrieve the ccbs_available_timer from + * \return The ccbs_available_timer from this configuration + */ +unsigned int ast_get_ccbs_available_timer(struct ast_cc_config_params *config); + +/*! + * \since 1.8 + * \brief Set the ccbs_available_timer + * \param config The configuration to set the ccbs_available_timer on + * \param value The new ccbs_available_timer we want to change to + * \retval void + */ +void ast_set_ccbs_available_timer(struct ast_cc_config_params *config, unsigned int value); + +/*! + * \since 1.8 + * \brief Get the cc_agent_dialstring + * \param config The configuration to retrieve the cc_agent_dialstring from + * \return The cc_agent_dialstring from this configuration + */ +const char *ast_get_cc_agent_dialstring(struct ast_cc_config_params *config); + +/*! + * \since 1.8 + * \brief Set the cc_agent_dialstring + * \param config The configuration to set the cc_agent_dialstring on + * \param value The new cc_agent_dialstring we want to change to + * \retval void + */ +void ast_set_cc_agent_dialstring(struct ast_cc_config_params *config, const char *const value); + +/*! + * \since 1.8 + * \brief Get the cc_max_agents + * \param config The configuration to retrieve the cc_max_agents from + * \return The cc_max_agents from this configuration + */ +unsigned int ast_get_cc_max_agents(struct ast_cc_config_params *config); + +/*! + * \since 1.8 + * \brief Set the cc_max_agents + * \param config The configuration to set the cc_max_agents on + * \param value The new cc_max_agents we want to change to + * \retval void + */ +void ast_set_cc_max_agents(struct ast_cc_config_params *config, unsigned int value); + +/*! + * \since 1.8 + * \brief Get the cc_max_monitors + * \param config The configuration to retrieve the cc_max_monitors from + * \return The cc_max_monitors from this configuration + */ +unsigned int ast_get_cc_max_monitors(struct ast_cc_config_params *config); + +/*! + * \since 1.8 + * \brief Set the cc_max_monitors + * \param config The configuration to set the cc_max_monitors on + * \param value The new cc_max_monitors we want to change to + * \retval void + */ +void ast_set_cc_max_monitors(struct ast_cc_config_params *config, unsigned int value); + +/*! + * \since 1.8 + * \brief Get the name of the callback_macro + * \param config The configuration to retrieve the callback_macro from + * \return The callback_macro name + */ +const char *ast_get_cc_callback_macro(struct ast_cc_config_params *config); + +/*! + * \since 1.8 + * \brief Set the callback_macro name + * \param config The configuration to set the callback_macro on + * \param value The new callback macro we want to change to + * \retval void + */ +void ast_set_cc_callback_macro(struct ast_cc_config_params *config, const char * const value); + +/* END CONFIGURATION FUNCTIONS */ + +/* BEGIN AGENT/MONITOR REGISTRATION API */ + +struct ast_cc_monitor_callbacks; + +/*! + * \since 1.8 + * \brief Register a set of monitor callbacks with the core + * + * \details + * This is made so that at monitor creation time, the proper callbacks + * may be installed and the proper .init callback may be called for the + * monitor to establish private data. + * + * \param callbacks The callbacks used by the monitor implementation + * \retval 0 Successfully registered + * \retval -1 Failure to register + */ +int ast_cc_monitor_register(const struct ast_cc_monitor_callbacks *callbacks); + +/*! + * \since 1.8 + * \brief Unregister a set of monitor callbacks with the core + * + * \details + * If a module which makes use of a CC monitor is unloaded, then it may + * unregister its monitor callbacks with the core. + * + * \param callbacks The callbacks used by the monitor implementation + * \retval 0 Successfully unregistered + * \retval -1 Failure to unregister + */ +void ast_cc_monitor_unregister(const struct ast_cc_monitor_callbacks *callbacks); + +struct ast_cc_agent_callbacks; + +/*! + * \since 1.8 + * \brief Register a set of agent callbacks with the core + * + * \details + * This is made so that at agent creation time, the proper callbacks + * may be installed and the proper .init callback may be called for the + * monitor to establish private data. + * + * \param callbacks The callbacks used by the agent implementation + * \retval 0 Successfully registered + * \retval -1 Failure to register + */ +int ast_cc_agent_register(const struct ast_cc_agent_callbacks *callbacks); + +/*! + * \since 1.8 + * \brief Unregister a set of agent callbacks with the core + * + * \details + * If a module which makes use of a CC agent is unloaded, then it may + * unregister its agent callbacks with the core. + * + * \param callbacks The callbacks used by the agent implementation + * \retval 0 Successfully unregistered + * \retval -1 Failure to unregister + */ +void ast_cc_agent_unregister(const struct ast_cc_agent_callbacks *callbacks); + +/* END AGENT/MONITOR REGISTRATION API */ + +/* BEGIN SECTION ON MONITORS AND MONITOR CALLBACKS */ + +/*! + * It is recommended that monitors use a pointer to + * an ast_cc_monitor_callbacks::type when creating + * an AST_CONTROL_CC frame. Since the generic monitor + * callbacks are opaque and channel drivers will wish + * to use that, this string is made globally available + * for all to use + */ +#define AST_CC_GENERIC_MONITOR_TYPE "generic" + +/*! + * Used to determine which type + * of monitor an ast_cc_device_monitor + * is. + */ +enum ast_cc_monitor_class { + AST_CC_DEVICE_MONITOR, + AST_CC_EXTENSION_MONITOR, +}; + +/*! + * \internal + * \brief An item in a CC interface tree. + * + * These are the individual items in an interface tree. + * The key difference between this structure and the ast_cc_interface + * is that this structure contains data which is intrinsic to the item's + * placement in the tree, such as who its parent is. + */ +struct ast_cc_monitor { + /*! + * Information regarding the interface. + */ + struct ast_cc_interface *interface; + /*! + * Every interface has an id that uniquely identifies it. It is + * formed by incrementing a counter. + */ + unsigned int id; + /*! + * The ID of this monitor's parent. If this monitor is at the + * top of the tree, then his parent will be 0. + */ + unsigned int parent_id; + /*! + * The instance of the CC core to which this monitor belongs + */ + int core_id; + /*! + * The type of call completion service offered by a device. + */ + enum ast_cc_service_type service_offered; + /*! + * \brief Name that should be used to recall specified interface + * + * \details + * When issuing a CC recall, some technologies will require + * that a name other than the device name is dialed. For instance, + * with SIP, a specific URI will be used which chan_sip will be able + * to recognize as being a CC recall. Similarly, ISDN will need a specific + * dial string to know that the call is a recall. + */ + char *dialstring; + /*! + * The ID of the available timer used by the current monitor + */ + int available_timer_id; + /*! + * Monitor callbacks + */ + const struct ast_cc_monitor_callbacks *callbacks; + /*! + * \brief Data that is private to a monitor technology + * + * Most channel drivers that implement CC monitors will have to + * allocate data that the CC core does not care about but which + * is vital to the operation of the monitor. This data is stored + * in this pointer so that the channel driver may use it as + * needed + */ + void *private_data; + AST_LIST_ENTRY(ast_cc_monitor) next; +}; + +/*! + * \brief Callbacks defined by CC monitors + * + * \note + * Every callback is called with the list of monitors locked. There + * are several public API calls that also will try to lock this lock. + * These public functions have a note in their doxygen stating so. + * As such, pay attention to the lock order you establish in these callbacks + * to ensure that you do not violate the lock order when calling + * the functions in this file with lock order notices. + */ +struct ast_cc_monitor_callbacks { + /*! + * \brief Type of monitor the callbacks belong to. + * + * \note + * Examples include "generic" and "SIP" + */ + const char *type; + /*! + * \brief Request CCSS. + * + * \param monitor CC core monitor control. + * \param available_timer_id The scheduler ID for the available timer. + * Will never be NULL for a device monitor. + * + * \details + * Perform whatever steps are necessary in order to request CC. + * In addition, the monitor implementation is responsible for + * starting the available timer in this callback. + * + * \retval 0 on success + * \retval -1 on failure. + */ + int (*request_cc)(struct ast_cc_monitor *monitor, int *available_timer_id); + /*! + * \brief Suspend monitoring. + * + * \param monitor CC core monitor control. + * + * \details + * Implementers must perform the necessary steps to suspend + * monitoring. + * + * \retval 0 on success + * \retval -1 on failure. + */ + int (*suspend)(struct ast_cc_monitor *monitor); + /*! + * \brief Status response to an ast_cc_monitor_status_request(). + * + * \param monitor CC core monitor control. + * \param devstate Current status of a Party A device. + * + * \details + * Alert a monitor as to the status of the agent for which + * the monitor had previously requested a status request. + * + * \note Zero or more responses may come as a result. + * + * \retval 0 on success + * \retval -1 on failure. + */ + int (*status_response)(struct ast_cc_monitor *monitor, enum ast_device_state devstate); + /*! + * \brief Unsuspend monitoring. + * + * \param monitor CC core monitor control. + * + * \details + * Perform the necessary steps to unsuspend monitoring. + * + * \retval 0 on success + * \retval -1 on failure. + */ + int (*unsuspend)(struct ast_cc_monitor *monitor); + /*! + * \brief Cancel the running available timer. + * + * \param monitor CC core monitor control. + * \param sched_id Available timer scheduler id to cancel. + * Will never be NULL for a device monitor. + * + * \details + * In most cases, this function will likely consist of just a + * call to AST_SCHED_DEL. It might have been possible to do this + * within the core, but unfortunately the mixture of sched_thread + * and sched usage in Asterisk prevents such usage. + * + * \retval 0 on success + * \retval -1 on failure. + */ + int (*cancel_available_timer)(struct ast_cc_monitor *monitor, int *sched_id); + /*! + * \brief Destroy private data on the monitor. + * + * \param private_data The private data pointer from the monitor. + * + * \details + * Implementers of this callback are responsible for destroying + * all heap-allocated data in the monitor's private_data pointer, including + * the private_data itself. + */ + void (*destructor)(void *private_data); +}; + +/*! + * \since 1.8 + * \brief Scheduler callback for available timer expiration + * + * \note + * When arming the available timer from within a device monitor, you MUST + * use this function as the callback for the scheduler. + * + * \param data A reference to the CC monitor on which the timer was running. + */ +int ast_cc_available_timer_expire(const void *data); + +/* END SECTION ON MONITORS AND MONITOR CALLBACKS */ + +/* BEGIN API FOR IN-CALL CC HANDLING */ + +/*! + * \since 1.8 + * + * \brief Mark the channel to ignore further CC activity. + * + * \details + * When a CC-capable application, such as Dial, has finished + * with all CC processing for a channel and knows that any further + * CC processing should be ignored, this function should be called. + * + * \param chan The channel for which further CC processing should be ignored. + * \retval void + */ +void ast_ignore_cc(struct ast_channel *chan); + +/*! + * \since 1.8 + * + * \brief Properly react to a CC control frame. + * + * \details + * When a CC-capable application, such as Dial, receives a frame + * of type AST_CONTROL_CC, then it may call this function in order + * to have the device which sent the frame added to the tree of interfaces + * which is kept on the inbound channel. + * + * \param inbound The inbound channel + * \param outbound The outbound channel (The one from which the CC frame was read) + * \param frame_data The ast_frame's data.ptr field. + * \retval void + */ +void ast_handle_cc_control_frame(struct ast_channel *inbound, struct ast_channel *outbound, void *frame_data); + +/*! + * \since 1.8 + * + * \brief Start the CC process on a call. + * + * \details + * Whenever a CC-capable application, such as Dial, wishes to + * engage in CC activity, it initiates the process by calling this + * function. If the CC core should discover that a previous application + * has called ast_ignore_cc on this channel or a "parent" channel, then + * the value of the ignore_cc integer passed in will be set nonzero. + * + * The ignore_cc parameter is a convenience parameter. It can save an + * application the trouble of trying to call CC APIs when it knows that + * it should just ignore further attempts at CC actions. + * + * \param chan The inbound channel calling the CC-capable application. + * \param[out] ignore_cc Will be set non-zero if no further CC actions need to be taken + * \retval 0 Success + * \retval -1 Failure + */ +int ast_cc_call_init(struct ast_channel *chan, int *ignore_cc); + +/*! + * \since 1.8 + * + * \brief Add a child dialstring to an extension monitor + * + * Whenever we request a channel, the parent extension monitor needs + * to store the dialstring of the device requested. The reason is so + * that we can call the device back during the recall even if we are + * not monitoring the device. + * + * \param incoming The caller's channel + * \param dialstring The dialstring used when requesting the outbound channel + * \param device_name The device name associated with the requested outbound channel + * \retval void + */ +void ast_cc_extension_monitor_add_dialstring(struct ast_channel *incoming, const char * const dialstring, const char * const device_name); + +/*! + * \since 1.8 + * \brief Check if the incoming CC request is within the bounds + * set by the cc_max_requests configuration option + * + * \details + * It is recommended that an entity which receives an incoming + * CC request calls this function before calling + * ast_cc_agent_accept_request. This way, immediate feedback can be + * given to the caller about why his request was rejected. + * + * If this is not called and a state change to CC_CALLER_REQUESTED + * is made, then the core will still not allow for the request + * to succeed. However, if done this way, it may not be obvious + * to the requestor why the request failed. + * + * \retval 0 Not within the limits. Fail. + * \retval non-zero Within the limits. Success. + */ +int ast_cc_request_is_within_limits(void); + +/*! + * \since 1.8 + * \brief Get the core id for the current call + * + * \details + * The main use of this function is for channel drivers + * who queue an AST_CONTROL_CC frame. A channel driver may + * call this function in order to get the core_id for what + * may become a CC request. This way, when monitor functions + * are called which use a core_id as a means of identification, + * the channel driver will have saved this information. + * + * The channel given to this function may be an inbound or outbound + * channel. Both will have the necessary info on it. + * + * \param chan The channel from which to get the core_id. + * \retval core_id on success + * \retval -1 Failure + */ +int ast_cc_get_current_core_id(struct ast_channel *chan); + +/* END API FOR IN-CALL CC HANDLING */ + +/*! + * \brief Structure with information about an outbound interface + * + * \details + * This structure is first created when an outbound interface indicates that + * it is capable of accepting a CC request. It is stored in a "tree" on a datastore on + * the caller's channel. Once an agent structure is created, the agent gains + * a reference to the tree of interfaces. If CC is requested, then the + * interface tree on the agent is converted into a tree of monitors. Each + * monitor will contain a pointer to an individual ast_cc_interface. Finally, + * the tree of interfaces is also present on a second datastore during a + * CC recall so that the CC_INTERFACES channel variable may be properly + * populated. + */ +struct ast_cc_interface { + /* What class of monitor is being offered here + */ + enum ast_cc_monitor_class monitor_class; + /*! + * \brief The type of monitor that should be used for this interface + * + * \details + * This will be something like "extension" "generic" or "SIP". + * This should point to a static const char *, so there is + * no reason to make a new copy. + */ + const char *monitor_type; + /*! + * The configuration parameters used for this interface + */ + struct ast_cc_config_params *config_params; + /* The name of the interface/extension. local channels will + * have 'exten@context' for a name. Other channel types will + * have 'tech/device' for a name. + */ + char device_name[1]; +}; + +/* BEGIN STRUCTURES FOR AGENTS */ + +struct ast_cc_agent { + /*! + * Which instance of the core state machine does this + * agent pertain to? + */ + unsigned int core_id; + /*! + * Callback functions needed for specific agent + * implementations + */ + const struct ast_cc_agent_callbacks *callbacks; + /*! + * Configuration parameters that affect this + * agent's operation. + */ + struct ast_cc_config_params *cc_params; + /*! + * \brief Flags for agent operation + * + * \details + * There are some attributes of certain agent types + * that can alter the behavior of certain CC functions. + * For a list of these flags, see the ast_cc_agent_flags + * enum + */ + unsigned int flags; + /*! Data specific to agent implementation */ + void *private_data; + /*! The name of the device which this agent + * represents/communicates with + */ + char device_name[1]; +}; + +struct ast_cc_agent_callbacks { + /*! + * \brief Type of agent the callbacks belong to. + * + * \note + * Examples are "SIP" "ISDN" and "generic" + */ + const char *type; + /*! + * \brief CC agent initialization. + * + * \param agent CC core agent control. + * \param chan Original channel the agent will attempt to recall. + * + * \details + * This callback is called when the CC core + * is initialized. Agents should allocate + * any private data necessary for the + * call and assign it to the private_data + * on the agent. Additionally, if any ast_cc_agent_flags + * are pertinent to the specific agent type, they should + * be set in this function as well. + * + * \retval 0 on success. + * \retval -1 on error. + */ + int (*init)(struct ast_cc_agent *agent, struct ast_channel *chan); + /*! + * \brief Start the offer timer. + * + * \param agent CC core agent control. + * + * \details + * This is called by the core when the caller hangs up after + * a call for which CC may be requested. The agent should + * begin the timer as configured. + * + * The primary reason why this functionality is left to + * the specific agent implementations is due to the differing + * use of schedulers throughout the code. Some channel drivers + * may already have a scheduler context they wish to use, and + * amongst those, some may use the ast_sched API while others + * may use the ast_sched_thread API, which are incompatible. + * + * \retval 0 on success. + * \retval -1 on error. + */ + int (*start_offer_timer)(struct ast_cc_agent *agent); + /*! + * \brief Stop the offer timer. + * + * \param agent CC core agent control. + * + * \details + * This callback is called by the CC core when the caller + * has requested CC. + * + * \retval 0 on success. + * \retval -1 on error. + */ + int (*stop_offer_timer)(struct ast_cc_agent *agent); + /*! + * \brief Acknowledge CC request. + * + * \param agent CC core agent control. + * + * \details + * When the core receives knowledge that a called + * party has accepted a CC request, it will call + * this callback. + * + * The duty of this is to accept a CC request from + * the caller by acknowledging receipt of that request. + */ + void (*ack)(struct ast_cc_agent *agent); + /*! + * \brief Request the status of the agent's device. + * + * \param agent CC core agent control. + * + * \details + * Asynchronous request for the status of any caller + * which may be a valid caller for the CC transaction. + * Status responses should be made using the + * ast_cc_status_response function. + * + * \retval 0 on success. + * \retval -1 on error. + */ + int (*status_request)(struct ast_cc_agent *agent); + /*! + * \brief Request for an agent's phone to stop ringing. + * + * \param agent CC core agent control. + * + * \details + * The usefulness of this is quite limited. The only specific + * known case for this is if Asterisk requests CC over an ISDN + * PTMP link as the TE side. If other phones are in the same + * recall group as the Asterisk server, and one of those phones + * picks up the recall notice, then Asterisk will receive a + * "stop ringing" notification from the NT side of the PTMP + * link. This indication needs to be passed to the phone + * on the other side of the Asterisk server which originally + * placed the call so that it will stop ringing. Since the + * phone may be of any type, it is necessary to have a callback + * that the core can know about. + * + * \retval 0 on success. + * \retval -1 on error. + */ + int (*stop_ringing)(struct ast_cc_agent *agent); + /*! + * \brief Let the caller know that the callee has become free + * but that the caller cannot attempt to call back because + * he is either busy or there is congestion on his line. + * + * \param agent CC core agent control. + * + * \details + * This is something that really only affects a scenario where + * a phone places a call over ISDN PTMP to Asterisk, who then + * connects over PTMP again to the ISDN network. For most agent + * types, there is no need to implement this callback at all + * because they don't really need to actually do anything in + * this situation. If you're having trouble understanding what + * the purpose of this callback is, then you can be safe simply + * not implementing it. + * + * \retval 0 on success. + * \retval -1 on error. + */ + int (*party_b_free)(struct ast_cc_agent *agent); + /*! + * \brief Begin monitoring a busy device. + * + * \param agent CC core agent control. + * + * \details + * The core will call this callback if the callee becomes + * available but the caller has reported that he is busy. + * The agent should begin monitoring the caller's device. + * When the caller becomes available again, the agent should + * call ast_cc_agent_caller_available. + * + * \retval 0 on success. + * \retval -1 on error. + */ + int (*start_monitoring)(struct ast_cc_agent *agent); + /*! + * \brief Alert the caller that it is time to try recalling. + * + * \param agent CC core agent control. + * + * \details + * The core will call this function when it receives notice + * that a monitored party has become available. + * + * The agent's job is to send a message to the caller to + * notify it of such a change. If the agent is able to + * discern that the caller is currently unavailable, then + * the agent should react by calling the ast_cc_caller_unavailable + * function. + * + * \retval 0 on success. + * \retval -1 on error. + */ + int (*callee_available)(struct ast_cc_agent *agent); + /*! + * \brief Destroy private data on the agent. + * + * \param agent CC core agent control. + * + * \details + * The core will call this function upon completion + * or failure of CC. + * + * \note + * The agent private_data pointer may be NULL if the agent + * constructor failed. + */ + void (*destructor)(struct ast_cc_agent *agent); +}; + +/*! + * \brief Call a callback on all agents of a specific type + * + * \details + * Since the container of CC core instances is private, and so + * are the items which the container contains, we have to provide + * an ao2_callback-like method so that a specific agent may be + * found or so that an operation can be made on all agents of + * a particular type. The first three arguments should be familiar + * to anyone who has used ao2_callback. The final argument is the + * type of agent you wish to have the callback called on. + * + * \note Since agents are refcounted, and this function returns + * a reference to the agent, it is imperative that you decrement + * the refcount of the agent once you have finished using it. + * + * \param flags astobj2 search flags + * \param function an ao2 callback function to call + * \param arg the argument to the callback function + * \param type The type of agents to call the callback on + */ +struct ast_cc_agent *ast_cc_agent_callback(int flags, ao2_callback_fn *function, void *arg, const char * const type); + +/* END STRUCTURES FOR AGENTS */ + +/* BEGIN STATE CHANGE API */ + +/*! + * \since 1.8 + * \brief Offer CC to a caller + * + * \details + * This function is called from ast_hangup if the caller is + * eligible to be offered call completion service. + * + * \param caller_chan The calling channel + * \retval -1 Error + * \retval 0 Success + */ +int ast_cc_offer(struct ast_channel *caller_chan); + +/*! + * \since 1.8 + * \brief Accept inbound CC request + * + * \details + * When a caller requests CC, this function should be called to let + * the core know that the request has been accepted. + * + * \param core_id core_id of the CC transaction + * \param debug optional string to print for debugging purposes + * \retval 0 Success + * \retval -1 Failure + */ +int __attribute__((format(printf, 2, 3))) ast_cc_agent_accept_request(int core_id, const char * const debug, ...); + +/*! + * \since 1.8 + * \brief Indicate that an outbound entity has accepted our CC request + * + * \details + * When we receive confirmation that an outbound device has accepted the + * CC request we sent it, this function must be called. + * + * \param core_id core_id of the CC transaction + * \param debug optional string to print for debugging purposes + * \retval 0 Success + * \retval -1 Failure + */ +int __attribute__((format(printf, 2, 3))) ast_cc_monitor_request_acked(int core_id, const char * const debug, ...); + +/*! + * \since 1.8 + * \brief Indicate that the caller is busy + * + * \details + * When the callee makes it known that he is available, the core + * will let the caller's channel driver know that it may attempt + * to let the caller know to attempt a recall. If the channel + * driver can detect, though, that the caller is busy, then + * the channel driver should call this function to let the CC + * core know. + * + * \param core_id core_id of the CC transaction + * \param debug optional string to print for debugging purposes + * \retval 0 Success + * \retval -1 Failure + */ +int __attribute__((format(printf, 2, 3))) ast_cc_agent_caller_busy(int core_id, const char * const debug, ...); + +/*! + * \since 1.8 + * \brief Indicate that a previously unavailable caller has become available + * + * \details + * If a monitor is suspended due to a caller becoming unavailable, then this + * function should be called to indicate that the caller has become available. + * + * \param core_id core_id of the CC transaction + * \param debug optional string to print for debugging purposes + * \retval 0 Success + * \retval -1 Failure + */ +int __attribute__((format(printf, 2, 3))) ast_cc_agent_caller_available(int core_id, const char * const debug, ...); + +/*! + * \since 1.8 + * \brief Tell the CC core that a caller is currently recalling + * + * \details + * The main purpose of this is so that the core can alert the monitor + * to stop its available timer since the caller has begun its recall + * phase. + * + * \param core_id core_id of the CC transaction + * \param debug optional string to print for debugging purposes + * \retval 0 Success + * \retval -1 Failure + */ +int __attribute__((format(printf, 2, 3))) ast_cc_agent_recalling(int core_id, const char * const debug, ...); + +/*! + * \since 1.8 + * \brief Indicate recall has been acknowledged + * + * \details + * When we receive confirmation that an endpoint has responded to our + * CC recall, we call this function. + * + * \param chan The inbound channel making the CC recall + * \param debug optional string to print for debugging purposes + * \retval 0 Success + * \retval -1 Failure + */ +int __attribute__((format(printf, 2, 3))) ast_cc_completed(struct ast_channel *chan, const char * const debug, ...); + +/*! + * \since 1.8 + * \brief Indicate failure has occurred + * + * \details + * If at any point a failure occurs, this is the function to call + * so that the core can initiate cleanup procedures. + * + * \param core_id core_id of the CC transaction + * \param debug optional string to print for debugging purposes + * \retval 0 Success + * \retval -1 Failure + */ +int __attribute__((format(printf, 2, 3))) ast_cc_failed(int core_id, const char * const debug, ...); + +/*! + * \since 1.8 + * \brief Indicate that a failure has occurred on a specific monitor + * + * \details + * If a monitor should detect that a failure has occurred when communicating + * with its endpoint, then ast_cc_monitor_failed should be called. The big + * difference between ast_cc_monitor_failed and ast_cc_failed is that ast_cc_failed + * indicates a global failure for a CC transaction, where as ast_cc_monitor_failed + * is localized to a particular monitor. When ast_cc_failed is called, the entire + * CC transaction is torn down. When ast_cc_monitor_failed is called, only the + * monitor on which the failure occurred is pruned from the tree of monitors. + * + * If there are no more devices left to monitor when this function is called, + * then the core will fail the CC transaction globally. + * + * \param core_id The core ID for the CC transaction + * \param monitor_name The name of the monitor on which the failure occurred + * \param debug A debug message to print to the CC log + * \return void + */ +int __attribute__((format(printf, 3, 4))) ast_cc_monitor_failed(int core_id, const char * const monitor_name, const char * const debug, ...); + +/* END STATE CHANGE API */ + +/*! + * The following are all functions which are required due to the unique + * case where Asterisk is acting as the NT side of an ISDN PTMP + * connection to the caller and as the TE side of an ISDN PTMP connection + * to the callee. In such a case, there are several times where the + * PTMP monitor needs information from the agent in order to formulate + * the appropriate messages to send. + */ + +/*! + * \brief Request the status of a caller or callers. + * + * \details + * When an ISDN PTMP monitor senses that the callee has become + * available, it needs to know the current status of the caller + * in order to determine the appropriate response to send to + * the caller. In order to do this, the monitor calls this function. + * Responses will arrive asynchronously. + * + * \note Zero or more responses may come as a result. + * + * \param core_id The core ID of the CC transaction + * + * \retval 0 Successfully requested status + * \retval -1 Failed to request status + */ +int ast_cc_monitor_status_request(int core_id); + +/*! + * \brief Response with a caller's current status + * + * \details + * When an ISDN PTMP monitor requests the caller's status, the + * agent must respond to the request using this function. For + * simplicity it is recommended that the devstate parameter + * be one of AST_DEVICE_INUSE or AST_DEVICE_NOT_INUSE. + * + * \param core_id The core ID of the CC transaction + * \param devstate The current state of the caller to which the agent pertains + * \retval 0 Successfully responded with our status + * \retval -1 Failed to respond with our status + */ +int ast_cc_agent_status_response(int core_id, enum ast_device_state devstate); + +/*! + * \brief Alert a caller to stop ringing + * + * \details + * When an ISDN PTMP monitor becomes available, it is assumed + * that the agent will then cause the caller's phone to ring. In + * some cases, this is literally what happens. In other cases, it may + * be that the caller gets a visible indication on his phone that he + * may attempt to recall the callee. If multiple callers are recalled + * (since it may be possible to have a group of callers configured as + * a single party A), and one of those callers picks up his phone, then + * the ISDN PTMP monitor will alert the other callers to stop ringing. + * The agent's stop_ringing callback will be called, and it is up to the + * agent's driver to send an appropriate message to make his caller + * stop ringing. + * + * \param core_id The core ID of the CC transaction + * \retval 0 Successfully requested for the phone to stop ringing + * \retval -1 Could not request for the phone to stop ringing + */ +int ast_cc_monitor_stop_ringing(int core_id); + +/*! + * \brief Alert a caller that though the callee has become free, the caller + * himself is not and may not call back. + * + * \details + * When an ISDN PTMP monitor senses that his monitored party has become + * available, he will request the status of the called party. If he determines + * that the caller is currently not available, then he will call this function + * so that an appropriate message is sent to the caller. + * + * Yes, you just read that correctly. The callee asks the caller what his + * current status is, and if the caller is currently unavailable, the monitor + * must send him a message anyway. WTF? + * + * This function results in the agent's party_b_free callback being called. + * It is most likely that you will not need to actually implement the + * party_b_free callback in an agent because it is not likely that you will + * need to or even want to send a caller a message indicating the callee's + * status if the caller himself is not also free. + * + * \param core_id The core ID of the CC transaction + * \retval 0 Successfully alerted the core that party B is free + * \retval -1 Could not alert the core that party B is free + */ +int ast_cc_monitor_party_b_free(int core_id); + +/* BEGIN API FOR USE WITH/BY MONITORS */ + +/*! + * \since 1.8 + * \brief Return the number of outstanding CC requests to a specific device + * + * \note + * This function will lock the list of monitors stored on every instance of + * the CC core. Callers of this function should be aware of this and avoid + * any potential lock ordering problems. + * + * \param name The name of the monitored device + * \param type The type of the monitored device (e.g. "generic") + * \return The number of CC requests for the monitor + */ +int ast_cc_monitor_count(const char * const name, const char * const type); + +/*! + * \since 1.8 + * \brief Alert the core that a device being monitored has become available. + * + * \note + * The code in the core will take care of making sure that the information gets passed + * up the ladder correctly. + * + * \param core_id The core ID of the corresponding CC transaction + * \retval 0 Request successfully queued + * \retval -1 Request could not be queued + */ +int __attribute__((format(printf, 2, 3))) ast_cc_monitor_callee_available(const int core_id, const char * const debug, ...); + +/* END API FOR USE WITH/BY MONITORS */ + +/* BEGIN API TO BE USED ON CC RECALL */ + +/*! + * \since 1.8 + * \brief Set up a CC recall datastore on a channel + * + * \details + * Implementers of protocol-specific CC agents will need to call this + * function in order for the channel to have the necessary interfaces + * to recall. + * + * This function must be called by the implementer once it has been detected + * that an inbound call is a cc_recall. After allocating the channel, call this + * function, followed by ast_cc_set_cc_interfaces_chanvar. While it would be nice to + * be able to have the core do this automatically, it just cannot be done given + * the current architecture. + */ +int ast_setup_cc_recall_datastore(struct ast_channel *chan, const int core_id); + +/*! + * \since 1.8 + * \brief Decide if a call to a particular channel is a CC recall + * + * \details + * When a CC recall happens, it is important on the called side to + * know that the call is a CC recall and not a normal call. This function + * will determine first if the call in question is a CC recall. Then it + * will determine based on the chan parameter if the channel is being + * called is being recalled. + * + * As a quick example, let's say a call is placed to SIP/1000 and SIP/1000 + * is currently on the phone. The caller requests CCBS. SIP/1000 finishes + * his call, and so the caller attempts to recall. Now, the dialplan + * administrator has set up this second call so that not only is SIP/1000 + * called, but also SIP/2000 is called. If SIP/1000's channel were passed + * to this function, the return value would be non-zero, but if SIP/2000's + * channel were passed into this function, then the return would be 0 since + * SIP/2000 was not one of the original devices dialed. + * + * \note + * This function may be called on a calling channel as well to + * determine if it is part of a CC recall. + * + * \note + * This function will lock the channel as well as the list of monitors + * on the channel datastore, though the locks are not held at the same time. Be + * sure that you have no potential lock order issues here. + * + * \param chan The channel to check + * \param core_id[out] If this is a valid CC recall, the core_id of the failed call + * will be placed in this output parameter + * \param monitor_type Clarify which type of monitor type we are looking for if this + * is happening on a called channel. For incoming channels, this parameter is not used. + * \retval 0 Either this is not a recall or it is but this channel is not part of the recall + * \retval non-zero This is a recall and the channel in question is directly involved. + */ +int ast_cc_is_recall(struct ast_channel *chan, int *core_id, const char * const monitor_type); + +/*! + * \since 1.8 + * \brief Get the associated monitor given the device name and core_id + * + * \details + * The function ast_cc_is_recall is helpful for determining if a call to + * a specific channel is a recall. However, once you have determined that + * this is a recall, you will most likely need access to the private data + * within the associated monitor. This function is what one uses to get + * that monitor. + * + * \note + * This function locks the list of monitors that correspond to the core_id + * passed in. Be sure that you have no potential lock order issues when + * calling this function. + * + * \param core_id The core ID to which this recall corresponds. This likely will + * have been obtained using the ast_cc_is_recall function + * \param device_name Which device to find the monitor for. + * + * \retval NULL Appropriate monitor does not exist + * \retval non-NULL The monitor to use for this recall + */ +struct ast_cc_monitor *ast_cc_get_monitor_by_recall_core_id(const int core_id, const char * const device_name); + +/*! + * \since 1.8 + * \brief Set the first level CC_INTERFACES channel variable for a channel. + * + * \note + * Implementers of protocol-specific CC agents should call this function after + * calling ast_setup_cc_recall_datastore. + * + * \note + * This function will lock the channel as well as the list of monitors stored + * on the channel's CC recall datastore, though neither are held at the same + * time. Callers of this function should be aware of potential lock ordering + * problems that may arise. + * + * \details + * The CC_INTERFACES channel variable will have the interfaces that should be + * called back for a specific PBX instance. + * + * \param chan The channel to set the CC_INTERFACES variable on + */ +int ast_cc_agent_set_interfaces_chanvar(struct ast_channel *chan); + +/*! + * \since 1.8 + * \brief Set the CC_INTERFACES channel variable for a channel using an + * extension@context as a starting point + * + * \details + * The CC_INTERFACES channel variable will have the interfaces that should be + * called back for a specific PBX instance. This version of the function is used + * mainly by chan_local, wherein we need to set CC_INTERFACES based on an extension + * and context that appear in the middle of the tree of dialed interfaces + * + * \note + * This function will lock the channel as well as the list of monitors stored + * on the channel's CC recall datastore, though neither are held at the same + * time. Callers of this function should be aware of potential lock ordering + * problems that may arise. + * + * \param chan The channel to set the CC_INTERFACES variable on + * \param extension The name of the extension for which we're setting the variable. + * This should be in the form of "exten@context" + */ +int ast_set_cc_interfaces_chanvar(struct ast_channel *chan, const char * const extension); + +/*! + * \since 1.8 + * \brief Make CCBS available in the case that ast_call fails + * + * In some situations, notably if a call-limit is reached in SIP, ast_call will fail + * due to Asterisk's knowing that the desired device is currently busy. In such a situation, + * CCBS should be made available to the caller. + * + * One caveat is that this may only be used if generic monitoring is being used. The reason + * is that since Asterisk determined that the device was busy without actually placing a call to it, + * the far end will have no idea what call we are requesting call completion for if we were to send + * a call completion request. + */ +void ast_cc_call_failed(struct ast_channel *incoming, struct ast_channel *outgoing, const char * const dialstring); + +/*! + * \since 1.8 + * \brief Callback made from ast_cc_callback for certain channel types + * + * \param inbound Incoming asterisk channel. + * \param cc_params The CC configuration parameters for the outbound target + * \param monitor_type The type of monitor to use when CC is requested + * \param device_name The name of the outbound target device. + * \param dialstring The dial string used when calling this specific interface + * \param private_data If a native monitor is being used, and some channel-driver-specific private + * data has been allocated, then this parameter should contain a pointer to that data. If using a generic + * monitor, this parameter should remain NULL. Note that if this function should fail at some point, + * it is the responsibility of the caller to free the private data upon return. + * + * \details + * For channel types that fail ast_request when the device is busy, we call into the + * channel driver with ast_cc_callback. This is the callback that is called in that + * case for each device found which could have been returned by ast_request. + * + * This function creates a CC control frame payload, simulating the act of reading + * it from the nonexistent outgoing channel's frame queue. We then handle this + * simulated frame just as we would a normal CC frame which had actually been queued + * by the channel driver. + */ +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); + +/*! + * \since 1.8 + * \brief Create a CC Control frame + * + * \details + * chan_dahdi is weird. It doesn't seem to actually queue frames when it needs to tell + * an application something. Instead it wakes up, tells the application that it has data + * ready, and then based on set flags, creates the proper frame type. For chan_dahdi, we + * provide this function. It provides us the data we need, and we'll make its frame for it. + * + * \param chan A channel involved in the call. What we want is on a datastore on both incoming and outgoing so either may be provided + * \param cc_params The CC configuration parameters for the outbound target + * \param monitor_type The type of monitor to use when CC is requested + * \param device_name The name of the outbound target device. + * \param dialstring The dial string used when calling this specific interface + * \param service What kind of CC service is being offered. (CCBS/CCNR/etc...) + * \param private_data If a native monitor is being used, and some channel-driver-specific private + * data has been allocated, then this parameter should contain a pointer to that data. If using a generic + * monitor, this parameter should remain NULL. Note that if this function should fail at some point, + * it is the responsibility of the caller to free the private data upon return. + * \param[out] frame. The frame we will be returning to the caller. It is vital that ast_frame_free be called on this frame since the + * payload will be allocated on the heap. + * \retval -1 Failure. At some point there was a failure. Do not attempt to use the frame in this case. + * \retval 0 Success + */ +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); + + +/*! + * \brief Callback made from ast_cc_callback for certain channel types + * \since 1.8 + * + * \param chan A channel involved in the call. What we want is on a datastore on both incoming and outgoing so either may be provided + * \param cc_params The CC configuration parameters for the outbound target + * \param monitor_type The type of monitor to use when CC is requested + * \param device_name The name of the outbound target device. + * \param dialstring The dial string used when calling this specific interface + * \param private_data If a native monitor is being used, and some channel-driver-specific private + * data has been allocated, then this parameter should contain a pointer to that data. If using a generic + * monitor, this parameter should remain NULL. Note that if this function should fail at some point, + * it is the responsibility of the caller to free the private data upon return. + * + * \details + * For channel types that fail ast_request when the device is busy, we call into the + * channel driver with ast_cc_callback. This is the callback that is called in that + * case for each device found which could have been returned by ast_request. + * + * \return Nothing + */ +typedef void (*ast_cc_callback_fn)(struct ast_channel *chan, struct ast_cc_config_params *cc_params, + const char *monitor_type, const char * const device_name, const char * const dialstring, void *private_data); + +/*! + * \since 1.8 + * \brief Run a callback for potential matching destinations. + * + * \note + * See the explanation in ast_channel_tech::cc_callback for more + * details. + * + * \param tech Channel technology to use + * \param dest Channel/group/peer or whatever the specific technology uses + * \param callback Function to call when a target is reached + * \retval Always 0, I guess. + */ +int ast_cc_callback(struct ast_channel *inbound, const char * const tech, const char * const dest, ast_cc_callback_fn callback); + +/*! + * \since 1.8 + * \brief Initialize CCSS + * + * Performs startup routines necessary for CC operation. + * + * \retval 0 Success + * \retval nonzero Failure + */ +int ast_cc_init(void); + +#endif /* _ASTERISK_CCSS_H */ diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index a32486a39..119dc42e8 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -148,6 +148,8 @@ extern "C" { #include "asterisk/linkedlists.h" #include "asterisk/stringfields.h" #include "asterisk/datastore.h" +#include "asterisk/channelstate.h" +#include "asterisk/ccss.h" #define DATASTORE_INHERIT_FOREVER INT_MAX @@ -507,6 +509,29 @@ struct ast_channel_tech { /*! \brief Get the unique identifier for the PVT, i.e. SIP call-ID for SIP */ const char * (* get_pvt_uniqueid)(struct ast_channel *chan); + + /*! \brief Call a function with cc parameters as a function parameter + * + * \details + * This is a highly specialized callback that is not likely to be needed in many + * channel drivers. When dealing with a busy channel, for instance, most channel + * drivers will successfully return a channel to the requester. Once called, the channel + * can then queue a busy frame when it receives an appropriate message from the far end. + * In such a case, the channel driver has the opportunity to also queue a CC frame. + * The parameters for the CC channel can be retrieved from the channel structure. + * + * For other channel drivers, notably those that deal with "dumb" phones, the channel + * driver will not return a channel when one is requested. In such a scenario, there is never + * an opportunity for the channel driver to queue a CC frame since the channel is never + * called. Furthermore, it is not possible to retrieve the CC configuration parameters + * for the desired channel because no channel is ever allocated or returned to the + * requester. In such a case, call completion may still be a viable option. What we do is + * pass the same string that the requester used originally to request the channel to the + * channel driver. The channel driver can then find any potential channels/devices that + * match the input and return call the designated callback with the device's call completion + * parameters as a parameter. + */ + int (* cc_callback)(struct ast_channel *inbound, const char *dest, ast_cc_callback_fn callback); }; struct ast_epoll_data; @@ -534,27 +559,6 @@ enum ast_channel_adsicpe { AST_ADSI_OFFHOOKONLY, }; -/*! - * \brief ast_channel states - * - * \note Bits 0-15 of state are reserved for the state (up/down) of the line - * Bits 16-32 of state are reserved for flags - */ -enum ast_channel_state { - AST_STATE_DOWN, /*!< Channel is down and available */ - AST_STATE_RESERVED, /*!< Channel is down, but reserved */ - AST_STATE_OFFHOOK, /*!< Channel is off hook */ - AST_STATE_DIALING, /*!< Digits (or equivalent) have been dialed */ - AST_STATE_RING, /*!< Line is ringing */ - AST_STATE_RINGING, /*!< Remote end is ringing */ - AST_STATE_UP, /*!< Line is up */ - AST_STATE_BUSY, /*!< Line is busy */ - AST_STATE_DIALING_OFFHOOK, /*!< Digits (or equivalent) have been dialed while offhook */ - AST_STATE_PRERING, /*!< Channel has detected an incoming call and is waiting for ring */ - - AST_STATE_MUTE = (1 << 16), /*!< Do not transmit voice data */ -}; - /*! * \brief Possible T38 states on channels */ @@ -950,9 +954,6 @@ int ast_channel_datastore_remove(struct ast_channel *chan, struct ast_datastore */ struct ast_datastore *ast_channel_datastore_find(struct ast_channel *chan, const struct ast_datastore_info *info, const char *uid); -/*! \brief Change the state of a channel */ -int ast_setstate(struct ast_channel *chan, enum ast_channel_state); - /*! * \brief Create a channel structure * \since 1.8 @@ -2756,6 +2757,95 @@ void ast_channel_queue_redirecting_update(struct ast_channel *chan, const struct * '0' */ int ast_channel_connected_line_macro(struct ast_channel *autoservice_chan, struct ast_channel *macro_chan, const void *connected_info, int caller, int frame); + +#include "asterisk/ccss.h" + +/*! + * \since 1.8 + * \brief Set up datastore with CCSS parameters for a channel + * + * \note + * If base_params is NULL, the channel will get the default + * values for all CCSS parameters. + * + * \details + * This function makes use of datastore operations on the channel, so + * it is important to lock the channel before calling this function. + * + * \param chan The channel to create the datastore on + * \param base_params CCSS parameters we wish to copy into the channel + * \retval 0 Success + * \retval -1 Failure + */ +int ast_channel_cc_params_init(struct ast_channel *chan, + const struct ast_cc_config_params *base_params); + +/*! + * \since 1.8 + * \brief Get the CCSS parameters from a channel + * + * \details + * This function makes use of datastore operations on the channel, so + * it is important to lock the channel before calling this function. + * + * \param chan Channel to retrieve parameters from + * \retval NULL Failure + * \retval non-NULL The parameters desired + */ +struct ast_cc_config_params *ast_channel_get_cc_config_params(struct ast_channel *chan); + + +/*! + * \since 1.8 + * \brief Get a device name given its channel structure + * + * \details + * A common practice in Asterisk is to determine the device being talked + * to by dissecting the channel name. For certain channel types, this is not + * accurate. For instance, an ISDN channel is named based on what B channel is + * used, not the device being communicated with. + * + * This function interfaces with a channel tech's queryoption callback to + * retrieve the name of the device being communicated with. If the channel does not + * implement this specific option, then the traditional method of using the channel + * name is used instead. + * + * \param chan The channel to retrieve the information from + * \param device_name[out] The buffer to place the device's name into + * \param name_buffer_length The allocated space for the device_name + * \return 0 always + */ +int ast_channel_get_device_name(struct ast_channel *chan, char *device_name, size_t name_buffer_length); + +/*! + * \since 1.8 + * \brief Find the appropriate CC agent type to use given a channel + * + * \details + * During call completion, we will need to create a call completion agent structure. To + * figure out the type of agent to construct, we need to ask the channel driver for the + * appropriate type. + * + * Prior to adding this function, the call completion core attempted to figure this + * out for itself by stripping the technology off the channel's name. However, in the + * case of chan_dahdi, there are multiple agent types registered, and so simply searching + * for an agent type called "DAHDI" is not possible. In a case where multiple agent types + * are defined, the channel driver must have a queryoption callback defined in its + * channel_tech, and the queryoption callback must handle AST_OPTION_CC_AGENT_TYPE + * + * If a channel driver does not have a queryoption callback or if the queryoption callback + * does not handle AST_OPTION_CC_AGENT_TYPE, then the old behavior of using the technology + * portion of the channel name is used instead. This is perfectly suitable for channel drivers + * whose channel technologies are a one-to-one match with the agent types defined within. + * + * Note that this function is only called when the agent policy on a given channel is set + * to "native." Generic agents' type can be determined automatically by the core. + * + * \param chan The channel for which we wish to retrieve the agent type + * \param[out] agent_type The type of agent the channel driver wants us to use + * \param size The size of the buffer to write to + */ +int ast_channel_get_cc_agent_type(struct ast_channel *chan, char *agent_type, size_t size); #if defined(__cplusplus) || defined(c_plusplus) } #endif diff --git a/include/asterisk/channelstate.h b/include/asterisk/channelstate.h new file mode 100644 index 000000000..f5f7392dd --- /dev/null +++ b/include/asterisk/channelstate.h @@ -0,0 +1,53 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2010, Digium, Inc. + * + * 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 Channel states + * \par See also: + * \arg \ref Def_Channel + * \arg \ref channel_drivers + */ + +#ifndef __AST_CHANNELSTATE_H__ +#define __AST_CHANNELSTATE_H__ + +#include "asterisk.h" + +/*! + * \brief ast_channel states + * + * \note Bits 0-15 of state are reserved for the state (up/down) of the line + * Bits 16-32 of state are reserved for flags + */ +enum ast_channel_state { + AST_STATE_DOWN, /*!< Channel is down and available */ + AST_STATE_RESERVED, /*!< Channel is down, but reserved */ + AST_STATE_OFFHOOK, /*!< Channel is off hook */ + AST_STATE_DIALING, /*!< Digits (or equivalent) have been dialed */ + AST_STATE_RING, /*!< Line is ringing */ + AST_STATE_RINGING, /*!< Remote end is ringing */ + AST_STATE_UP, /*!< Line is up */ + AST_STATE_BUSY, /*!< Line is busy */ + AST_STATE_DIALING_OFFHOOK, /*!< Digits (or equivalent) have been dialed while offhook */ + AST_STATE_PRERING, /*!< Channel has detected an incoming call and is waiting for ring */ + + AST_STATE_MUTE = (1 << 16), /*!< Do not transmit voice data */ +}; + +/*! \brief Change the state of a channel */ +int ast_setstate(struct ast_channel *chan, enum ast_channel_state); + +#endif /* __AST_CHANNELSTATE_H__ */ diff --git a/include/asterisk/devicestate.h b/include/asterisk/devicestate.h index 4c516870e..2a53ebb46 100644 --- a/include/asterisk/devicestate.h +++ b/include/asterisk/devicestate.h @@ -37,7 +37,7 @@ #ifndef _ASTERISK_DEVICESTATE_H #define _ASTERISK_DEVICESTATE_H -#include "asterisk/channel.h" +#include "asterisk/channelstate.h" #if defined(__cplusplus) || defined(c_plusplus) extern "C" { diff --git a/include/asterisk/frame.h b/include/asterisk/frame.h index 30119d49b..68a0c7eb6 100644 --- a/include/asterisk/frame.h +++ b/include/asterisk/frame.h @@ -324,7 +324,8 @@ enum ast_control_frame_type { AST_CONTROL_CONNECTED_LINE = 22,/*!< Indicate connected line has changed */ AST_CONTROL_REDIRECTING = 23, /*!< Indicate redirecting id has changed */ AST_CONTROL_T38_PARAMETERS = 24, /*! T38 state change request/notification with parameters */ - AST_CONTROL_SRCCHANGE = 25, /*!< Media source has changed and requires a new RTP SSRC */ + AST_CONTROL_CC = 25, /*!< Indication that Call completion service is possible */ + AST_CONTROL_SRCCHANGE = 26, /*!< Media source has changed and requires a new RTP SSRC */ }; enum ast_control_t38 { @@ -433,6 +434,12 @@ enum ast_control_transfer { /*! Get or set the fax tone detection state of the channel */ #define AST_OPTION_FAX_DETECT 15 +/*! Get the device name from the channel */ +#define AST_OPTION_DEVICE_NAME 16 + +/*! Get the CC agent type from the channel */ +#define AST_OPTION_CC_AGENT_TYPE 17 + struct oprmode { struct ast_channel *peer; int mode; diff --git a/include/asterisk/manager.h b/include/asterisk/manager.h index 91c43d3fe..dd7c160f4 100644 --- a/include/asterisk/manager.h +++ b/include/asterisk/manager.h @@ -82,6 +82,7 @@ #define EVENT_FLAG_ORIGINATE (1 << 12) /* Originate a call to an extension */ #define EVENT_FLAG_AGI (1 << 13) /* AGI events */ #define EVENT_FLAG_HOOKRESPONSE (1 << 14) /* Hook Response */ +#define EVENT_FLAG_CC (1 << 15) /* Call Completion events */ /*@} */ /*! \brief Export manager structures */ diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h index 770f4d2f5..e7b809d4c 100644 --- a/include/asterisk/rtp_engine.h +++ b/include/asterisk/rtp_engine.h @@ -70,6 +70,7 @@ extern "C" { #endif #include "asterisk/astobj2.h" +#include "asterisk/frame.h" /* Maximum number of payloads supported */ #define AST_RTP_MAX_PT 256 diff --git a/include/asterisk/xml.h b/include/asterisk/xml.h index 5c78cc9c0..2c30986cc 100644 --- a/include/asterisk/xml.h +++ b/include/asterisk/xml.h @@ -45,6 +45,14 @@ int ast_xml_finish(void); */ struct ast_xml_doc *ast_xml_open(char *filename); +/*! \brief Open an XML document that resides in memory. + * \param buffer The address where the document is stored + * \size The number of bytes in the document + * \retval NULL on error. + * \retval The ast_xml_doc reference to the open document. + */ +struct ast_xml_doc *ast_xml_read_memory(char *buffer, size_t size); + /*! \brief Close an already open document and free the used * structure. * \retval doc The document reference. @@ -90,6 +98,8 @@ const char *ast_xml_get_attribute(struct ast_xml_node *node, const char *attrnam * \retval The node on success. */ struct ast_xml_node *ast_xml_find_element(struct ast_xml_node *root_node, const char *name, const char *attrname, const char *attrvalue); +struct ast_xml_ns *ast_xml_find_namespace(struct ast_xml_doc *doc, struct ast_xml_node *node, const char *ns_name); +const char *ast_xml_get_ns_href(struct ast_xml_ns *ns); /*! \brief Get an element content string. * \param node Node from where to get the string. diff --git a/main/asterisk.c b/main/asterisk.c index 8f557f736..57ccc3f63 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -140,6 +140,7 @@ int daemon(int, int); /* defined in libresolv of all places */ #include "asterisk/buildinfo.h" #include "asterisk/xmldoc.h" #include "asterisk/poll-compat.h" +#include "asterisk/ccss.h" #include "asterisk/test.h" #include "../defaults.h" @@ -3684,6 +3685,11 @@ int main(int argc, char *argv[]) exit(1); } + if (ast_cc_init()) { + printf("%s", term_quit()); + exit(1); + } + if ((moduleresult = load_modules(0))) { /* Load modules */ printf("%s", term_quit()); exit(moduleresult == -2 ? 2 : 1); diff --git a/main/ccss.c b/main/ccss.c new file mode 100644 index 000000000..4e616011b --- /dev/null +++ b/main/ccss.c @@ -0,0 +1,4157 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2010, Digium, Inc. + * + * Mark Michelson + * + * 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 + */ + +#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 + + + Request call completion service for previous call + + + + Request call completion service for a previously failed + call attempt. + + + + + Cancel call completion service + + + + Cancel a Call Completion Request. + + + ***/ + +/* 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; +} diff --git a/main/channel.c b/main/channel.c index 5be973a49..03c657cff 100644 --- a/main/channel.c +++ b/main/channel.c @@ -2239,6 +2239,7 @@ int ast_hangup(struct ast_channel *chan) } ast_channel_unlock(chan); + ast_cc_offer(chan); ast_manager_event(chan, EVENT_FLAG_CALL, "Hangup", "Channel: %s\r\n" "Uniqueid: %s\r\n" @@ -3611,6 +3612,7 @@ static int attribute_const is_visible_indication(enum ast_control_frame_type con case AST_CONTROL_TRANSFER: case AST_CONTROL_T38_PARAMETERS: case _XXX_AST_CONTROL_T38: + case AST_CONTROL_CC: break; case AST_CONTROL_CONGESTION: @@ -3754,6 +3756,7 @@ int ast_indicate_data(struct ast_channel *chan, int _condition, case AST_CONTROL_TRANSFER: case AST_CONTROL_CONNECTED_LINE: case AST_CONTROL_REDIRECTING: + case AST_CONTROL_CC: /* Nothing left to do for these. */ res = 0; break; @@ -4477,6 +4480,7 @@ struct ast_channel *__ast_request_and_dial(const char *type, format_t format, co case AST_CONTROL_SRCCHANGE: case AST_CONTROL_CONNECTED_LINE: case AST_CONTROL_REDIRECTING: + case AST_CONTROL_CC: case -1: /* Ignore -- just stopping indications */ break; @@ -7444,6 +7448,107 @@ int ast_channel_connected_line_macro(struct ast_channel *autoservice_chan, struc return retval; } +static void *channel_cc_params_copy(void *data) +{ + const struct ast_cc_config_params *src = data; + struct ast_cc_config_params *dest = ast_cc_config_params_init(); + if (!dest) { + return NULL; + } + ast_cc_copy_config_params(dest, src); + return dest; +} + +static void channel_cc_params_destroy(void *data) +{ + struct ast_cc_config_params *cc_params = data; + ast_cc_config_params_destroy(cc_params); +} + +static const struct ast_datastore_info cc_channel_datastore_info = { + .type = "Call Completion", + .duplicate = channel_cc_params_copy, + .destroy = channel_cc_params_destroy, +}; + +int ast_channel_cc_params_init(struct ast_channel *chan, + const struct ast_cc_config_params *base_params) +{ + struct ast_cc_config_params *cc_params; + struct ast_datastore *cc_datastore; + + if (!(cc_params = ast_cc_config_params_init())) { + return -1; + } + + if (!(cc_datastore = ast_datastore_alloc(&cc_channel_datastore_info, NULL))) { + ast_cc_config_params_destroy(cc_params); + return -1; + } + + if (base_params) { + ast_cc_copy_config_params(cc_params, base_params); + } + cc_datastore->data = cc_params; + ast_channel_datastore_add(chan, cc_datastore); + return 0; +} + +struct ast_cc_config_params *ast_channel_get_cc_config_params(struct ast_channel *chan) +{ + struct ast_datastore *cc_datastore; + + if (!(cc_datastore = ast_channel_datastore_find(chan, &cc_channel_datastore_info, NULL))) { + /* If we can't find the datastore, it almost definitely means that the channel type being + * used has not had its driver modified to parse CC config parameters. The best action + * to take here is to create the parameters on the spot with the defaults set. + */ + if (ast_channel_cc_params_init(chan, NULL)) { + return NULL; + } + if (!(cc_datastore = ast_channel_datastore_find(chan, &cc_channel_datastore_info, NULL))) { + /* Should be impossible */ + return NULL; + } + } + + ast_assert(cc_datastore->data != NULL); + return cc_datastore->data; +} + +int ast_channel_get_device_name(struct ast_channel *chan, char *device_name, size_t name_buffer_length) +{ + int len = name_buffer_length; + char *dash; + if (!ast_channel_queryoption(chan, AST_OPTION_DEVICE_NAME, device_name, &len, 0)) { + return 0; + } + + /* Dang. Do it the old-fashioned way */ + ast_copy_string(device_name, chan->name, name_buffer_length); + if ((dash = strrchr(device_name, '-'))) { + *dash = '\0'; + } + + return 0; +} + +int ast_channel_get_cc_agent_type(struct ast_channel *chan, char *agent_type, size_t size) +{ + int len = size; + char *slash; + + if (!ast_channel_queryoption(chan, AST_OPTION_CC_AGENT_TYPE, agent_type, &len, 0)) { + return 0; + } + + ast_copy_string(agent_type, chan->name, size); + if ((slash = strchr(agent_type, '/'))) { + *slash = '\0'; + } + return 0; +} + /* DO NOT PUT ADDITIONAL FUNCTIONS BELOW THIS BOUNDARY * * ONLY FUNCTIONS FOR PROVIDING BACKWARDS ABI COMPATIBILITY BELONG HERE diff --git a/main/manager.c b/main/manager.c index f811987f7..0456990b4 100644 --- a/main/manager.c +++ b/main/manager.c @@ -980,6 +980,7 @@ static const struct permalias { { EVENT_FLAG_DIALPLAN, "dialplan" }, { EVENT_FLAG_ORIGINATE, "originate" }, { EVENT_FLAG_AGI, "agi" }, + { EVENT_FLAG_CC, "cc" }, { INT_MAX, "all" }, { 0, "none" }, }; diff --git a/main/xml.c b/main/xml.c index 36e7dd812..0f93abc54 100644 --- a/main/xml.c +++ b/main/xml.c @@ -23,6 +23,7 @@ #include "asterisk.h" #include "asterisk/xml.h" +#include "asterisk/logger.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision$") @@ -67,6 +68,25 @@ struct ast_xml_doc *ast_xml_open(char *filename) return (struct ast_xml_doc *) doc; } +struct ast_xml_doc *ast_xml_read_memory(char *buffer, size_t size) +{ + xmlDoc *doc; + + if (!buffer) { + return NULL; + } + + if (!(doc = xmlParseMemory(buffer, (int) size))) { + /* process xinclude elements. */ + if (xmlXIncludeProcess(doc) < 0) { + xmlFreeDoc(doc); + return NULL; + } + } + + return (struct ast_xml_doc *) doc; +} + void ast_xml_close(struct ast_xml_doc *doc) { if (!doc) { @@ -164,6 +184,16 @@ struct ast_xml_node *ast_xml_find_element(struct ast_xml_node *root_node, const return NULL; } +struct ast_xml_ns *ast_xml_find_namespace(struct ast_xml_doc *doc, struct ast_xml_node *node, const char *ns_name) { + xmlNsPtr ns = xmlSearchNs((xmlDocPtr) doc, (xmlNodePtr) node, (xmlChar *) ns_name); + return (struct ast_xml_ns *) ns; +} + +const char *ast_xml_get_ns_href(struct ast_xml_ns *ns) +{ + return (const char *) ((xmlNsPtr) ns)->href; +} + const char *ast_xml_get_text(struct ast_xml_node *node) { if (!node) { -- cgit v1.2.3