diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c index b28c2d675a..5781504289 100644 --- a/channels/chan_pjsip.c +++ b/channels/chan_pjsip.c @@ -58,6 +58,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/stasis_channels.h" #include "asterisk/indications.h" #include "asterisk/threadstorage.h" +#include "asterisk/features_config.h" +#include "asterisk/pickup.h" #include "asterisk/res_pjsip.h" #include "asterisk/res_pjsip_session.h" @@ -1967,6 +1969,49 @@ static int chan_pjsip_incoming_request(struct ast_sip_session *session, struct p return 0; } +static int call_pickup_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata) +{ + struct ast_features_pickup_config *pickup_cfg = ast_get_chan_features_pickup_config(session->channel); + struct ast_channel *chan; + + if (!pickup_cfg) { + ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension.\n"); + return 0; + } + + if (strcmp(session->exten, pickup_cfg->pickupexten)) { + ao2_ref(pickup_cfg, -1); + return 0; + } + ao2_ref(pickup_cfg, -1); + + /* We can't directly use session->channel because the pickup operation will cause a masquerade to occur, + * changing the channel pointer in session to a different channel. To ensure we work on the right channel + * we store a pointer locally before we begin and keep a reference so it remains valid no matter what. + */ + chan = ast_channel_ref(session->channel); + if (ast_pickup_call(chan)) { + ast_channel_hangupcause_set(chan, AST_CAUSE_CALL_REJECTED); + } else { + ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL_CLEARING); + } + /* A hangup always occurs because the pickup operation will have either failed resulting in the call + * needing to be hung up OR the pickup operation was a success and the channel we now have is actually + * the channel that was replaced, which should be hung up since it is literally in limbo not connected + * to anything at all. + */ + ast_hangup(chan); + ast_channel_unref(chan); + + return 1; +} + +static struct ast_sip_session_supplement call_pickup_supplement = { + .method = "INVITE", + .priority = AST_SIP_SUPPLEMENT_PRIORITY_LAST - 1, + .incoming_request = call_pickup_incoming_request, +}; + static int pbx_start_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata) { int res; @@ -2106,9 +2151,16 @@ static int load_module(void) goto end; } + if (ast_sip_session_register_supplement(&call_pickup_supplement)) { + ast_log(LOG_ERROR, "Unable to register PJSIP call pickup supplement\n"); + ast_sip_session_unregister_supplement(&chan_pjsip_supplement); + goto end; + } + if (ast_sip_session_register_supplement(&pbx_start_supplement)) { ast_log(LOG_ERROR, "Unable to register PJSIP pbx start supplement\n"); ast_sip_session_unregister_supplement(&chan_pjsip_supplement); + ast_sip_session_unregister_supplement(&call_pickup_supplement); goto end; } @@ -2116,6 +2168,7 @@ static int load_module(void) ast_log(LOG_ERROR, "Unable to register PJSIP ACK supplement\n"); ast_sip_session_unregister_supplement(&pbx_start_supplement); ast_sip_session_unregister_supplement(&chan_pjsip_supplement); + ast_sip_session_unregister_supplement(&call_pickup_supplement); goto end; } @@ -2154,6 +2207,7 @@ static int unload_module(void) ast_sip_session_unregister_supplement(&chan_pjsip_supplement); ast_sip_session_unregister_supplement(&pbx_start_supplement); ast_sip_session_unregister_supplement(&chan_pjsip_ack_supplement); + ast_sip_session_unregister_supplement(&call_pickup_supplement); ast_custom_function_unregister(&media_offer_function); ast_custom_function_unregister(&chan_pjsip_dial_contacts_function); diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c index 82c7a3f229..6acbc0c6d3 100644 --- a/res/res_pjsip_session.c +++ b/res/res_pjsip_session.c @@ -43,6 +43,8 @@ #include "asterisk/sdp_srtp.h" #include "asterisk/dsp.h" #include "asterisk/acl.h" +#include "asterisk/features_config.h" +#include "asterisk/pickup.h" #define SDP_HANDLER_BUCKETS 11 @@ -1349,12 +1351,27 @@ static enum sip_get_destination_result get_destination(struct ast_sip_session *s { pjsip_uri *ruri = rdata->msg_info.msg->line.req.uri; pjsip_sip_uri *sip_ruri; + struct ast_features_pickup_config *pickup_cfg; + const char *pickupexten; + if (!PJSIP_URI_SCHEME_IS_SIP(ruri) && !PJSIP_URI_SCHEME_IS_SIPS(ruri)) { return SIP_GET_DEST_UNSUPPORTED_URI; } + sip_ruri = pjsip_uri_get_uri(ruri); ast_copy_pj_str(session->exten, &sip_ruri->user, sizeof(session->exten)); - if (ast_exists_extension(NULL, session->endpoint->context, session->exten, 1, NULL)) { + + pickup_cfg = ast_get_chan_features_pickup_config(session->channel); + if (!pickup_cfg) { + ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n"); + pickupexten = ""; + } else { + pickupexten = ast_strdupa(pickup_cfg->pickupexten); + ao2_ref(pickup_cfg, -1); + } + + if (!strcmp(session->exten, pickupexten) || + ast_exists_extension(NULL, session->endpoint->context, session->exten, 1, NULL)) { return SIP_GET_DEST_EXTEN_FOUND; } /* XXX In reality, we'll likely have further options so that partial matches