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 --- 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 +- 8 files changed, 8415 insertions(+), 3909 deletions(-) (limited to 'channels') 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 -- cgit v1.2.3