From 2446e2a6db7c5dd168f9320d8d48bc200edabd32 Mon Sep 17 00:00:00 2001 From: Gareth Palmer Date: Thu, 4 Apr 2024 13:52:37 +1300 Subject: [PATCH] Add patches for 18.22.0 and 20.7.0. --- README.md | 4 +- asterisk/cisco-usecallmanager-18.22.0.patch | 8756 ++++++++++++++++++ asterisk/cisco-usecallmanager-20.7.0.patch | 8765 +++++++++++++++++++ 3 files changed, 17523 insertions(+), 2 deletions(-) create mode 100644 asterisk/cisco-usecallmanager-18.22.0.patch create mode 100644 asterisk/cisco-usecallmanager-20.7.0.patch diff --git a/README.md b/README.md index a2032d1..552031c 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ for more information. ## Latest Patch Versions -* Asterisk 18: [cisco-usecallmanager-18.21.0.patch](asterisk/cisco-usecallmanager-18.21.0.patch). -* Asterisk 20: [cisco-usecallmanager-20.6.0.patch](asterisk/cisco-usecallmanager-20.6.0.patch). +* Asterisk 18: [cisco-usecallmanager-18.22.0.patch](asterisk/cisco-usecallmanager-18.22.0.patch). +* Asterisk 20: [cisco-usecallmanager-20.7.0.patch](asterisk/cisco-usecallmanager-20.7.0.patch). ## OpenConnect Patch diff --git a/asterisk/cisco-usecallmanager-18.22.0.patch b/asterisk/cisco-usecallmanager-18.22.0.patch new file mode 100644 index 0000000..92c8f68 --- /dev/null +++ b/asterisk/cisco-usecallmanager-18.22.0.patch @@ -0,0 +1,8756 @@ +diff -durN asterisk-18.22.0.orig/channels/chan_sip.c asterisk-18.22.0/channels/chan_sip.c +--- asterisk-18.22.0.orig/channels/chan_sip.c 2024-04-04 00:47:56.248995269 +1300 ++++ asterisk-18.22.0/channels/chan_sip.c 2024-04-04 00:48:19.500358795 +1300 +@@ -367,6 +367,67 @@ + application is only available if TEST_FRAMEWORK is defined. + + ++ ++ ++ Page a series of Cisco USECALLMANAGER phones ++ ++ ++ ++ ++ Name of the SIP peer to page ++ ++ ++ Name of the second peer to page, additional peers are ++ specified as peer&peer2&peer3... ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Using the RTP streaming API, send a request to the specified peers to ++ receive RTP audio. Supported codecs are G711 (mulaw and alaw), G722 and ++ G729a. RTP is transmitted as unicast unless the m() option is used. ++ ++ + + + Gets the specified SIP header from an incoming INVITE message. +@@ -509,6 +570,27 @@ + + Preferred codec index number x (beginning with zero). + ++ ++ The vmexten for this peer. ++ ++ ++ Is DoNotDisturb set on this peer (yes/no). ++ ++ ++ The call forwarding extension for this peer. ++ ++ ++ Is HuntGroup login set on this peer (yes/no). ++ ++ ++ The Call-ID of the REGISTER dialog. ++ ++ ++ The device name of the Cisco USECALLMANAGER peer ++ ++ ++ The line index of the Cisco USECALLMANAGER peer ++ + + + +@@ -727,7 +809,9 @@ + { CPIM_PIDF_XML, "presence", "application/cpim-pidf+xml", "cpim-pidf+xml" }, /* RFC 3863 */ + { PIDF_XML, "presence", "application/pidf+xml", "pidf+xml" }, /* RFC 3863 */ + { XPIDF_XML, "presence", "application/xpidf+xml", "xpidf+xml" }, /* Pre-RFC 3863 with MS additions */ +- { MWI_NOTIFICATION, "message-summary", "application/simple-message-summary", "mwi" } /* RFC 3842: Mailbox notification */ ++ { MWI_NOTIFICATION, "message-summary", "application/simple-message-summary", "mwi" }, /* RFC 3842: Mailbox notification */ ++ { FEATURE_EVENTS, "as-feature-event", "application/x-as-feature-event+xml", "as-feature-event" }, /* EMCA-323 application server feature events */ ++ { REMOTECC_XML, "refer", "application/x-cisco-remotecc-request+xml", "remotecc" } /* Cisco remotecc request/respones */ + }; + + /*! \brief The core structure to setup dialogs. We parse incoming messages by using +@@ -892,6 +976,7 @@ + static struct stasis_subscription *network_change_sub; /*!< subscription id for network change events */ + static struct stasis_subscription *acl_change_sub; /*!< subscription id for named ACL system change events */ + static int network_change_sched_id = -1; ++static struct stasis_subscription *pickup_notify_sub; /*!< subscription id for call ringing events */ + + static char used_context[AST_MAX_CONTEXT]; /*!< name of automatically created context for unloading */ + +@@ -926,6 +1011,68 @@ + */ + static int sipdebug_text; + ++/*! \brief Remotecc applications */ ++enum { ++ REMOTECC_CONFLIST = 1, ++ REMOTECC_CALLBACK = 2 ++}; ++ ++/*! \brief Contains the parsed-out xml elements from a remotecc request */ ++struct remotecc_dialog { ++ char *callid; ++ char *localtag; ++ char *remotetag; ++}; ++ ++struct remotecc_data { ++ char *softkeyevent; ++ struct remotecc_dialog dialogid; ++ struct remotecc_dialog consultdialogid; ++ struct remotecc_dialog joindialogid; ++ int applicationid; ++ int confid; ++ char *usercalldata; ++}; ++ ++/*! \brief Information required to start or join an ad-hoc conference */ ++struct conference_data { ++ struct sip_pvt *pvt; ++ AST_LIST_HEAD_NOLOCK(, sip_selected) selected; ++ int joining:1; ++ AST_DECLARE_STRING_FIELDS( ++ AST_STRING_FIELD(callid); ++ AST_STRING_FIELD(tag); ++ AST_STRING_FIELD(theirtag); ++ AST_STRING_FIELD(join_callid); ++ AST_STRING_FIELD(join_tag); ++ AST_STRING_FIELD(join_theirtag); ++ ); ++}; ++ ++/*! \brief Informtion required to park a call */ ++struct park_data { ++ struct sip_pvt *pvt; ++ struct ast_channel *chan; ++ int monitor:1; ++ AST_DECLARE_STRING_FIELDS( ++ AST_STRING_FIELD(context); ++ AST_STRING_FIELD(callid); ++ AST_STRING_FIELD(tag); ++ AST_STRING_FIELD(theirtag); ++ AST_STRING_FIELD(uniqueid); ++ ); ++}; ++ ++/*! \brief Information required to record a call */ ++struct record_data { ++ int outgoing:1; ++ AST_DECLARE_STRING_FIELDS( ++ AST_STRING_FIELD(callid); ++ AST_STRING_FIELD(tag); ++ AST_STRING_FIELD(theirtag); ++ ); ++}; ++ + static const struct _map_x_s referstatusstrings[] = { + { REFER_IDLE, "" }, + { REFER_SENT, "Request sent" }, +@@ -979,11 +1126,17 @@ + + #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 int presence_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, + }; ++ ++static const struct sip_esc_publish_callbacks presence_esc_publish_callbacks = { ++ .initial_handler = presence_esc_publish_handler, ++ .modify_handler = presence_esc_publish_handler, ++}; + #endif + + /*! +@@ -1006,6 +1159,7 @@ + } event_state_compositors [] = { + #ifdef HAVE_LIBXML2 + {CALL_COMPLETION, "call-completion", &cc_esc_publish_callbacks}, ++ {PRESENCE, "presence", &presence_esc_publish_callbacks}, + #endif + }; + +@@ -1071,6 +1225,10 @@ + static int temp_pvt_init(void *); + static void temp_pvt_cleanup(void *); + ++/*! \brief The ad-hoc conference list */ ++static AST_LIST_HEAD_STATIC(conferences, sip_conference); ++static int next_confid = 0; ++ + /*! \brief A per-thread temporary pvt structure */ + AST_THREADSTORAGE_CUSTOM(ts_temp_pvt, temp_pvt_init, temp_pvt_cleanup); + +@@ -1177,6 +1335,7 @@ + /*--- PBX interface functions */ + static struct ast_channel *sip_request_call(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *dest, int *cause); + static int sip_devicestate(const char *data); ++static int sip_presencestate(const char *data, char **subtype, char **message); + static int sip_sendtext(struct ast_channel *ast, const char *text); + static int sip_call(struct ast_channel *ast, const char *dest, int timeout); + static int sip_sendhtml(struct ast_channel *chan, int subclass, const char *data, int datalen); +@@ -1202,7 +1361,8 @@ + static int sipsock_read(int *id, int fd, short events, void *ignore); + static int __sip_xmit(struct sip_pvt *p, struct ast_str *data); + static int __sip_reliable_xmit(struct sip_pvt *p, uint32_t seqno, int resp, struct ast_str *data, int fatal, int sipmethod); +-static void add_cc_call_info_to_response(struct sip_pvt *p, struct sip_request *resp); ++static void add_cc_call_info(struct sip_request *resp, struct sip_pvt *p); ++static void add_remotecc_call_info(struct sip_request *req, struct sip_pvt *p); + 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 ast_sockaddr *addr, int useglobal_nat, const int intended_method, const struct sip_request *req, const char *msg); +@@ -1212,6 +1372,7 @@ + static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable, int oldsdp, int rpid); + static int transmit_response_with_unsupported(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *unsupported); + static int transmit_response_with_auth(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *rand, enum xmittype reliable, const char *header, int stale); ++static int transmit_response_with_optionsind(struct sip_pvt *p, const struct sip_request *req); + static int transmit_provisional_response(struct sip_pvt *p, const char *msg, const struct sip_request *req, int with_sdp); + static int transmit_response_with_allow(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable); + static void transmit_fake_auth_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable); +@@ -1225,6 +1386,7 @@ + static int transmit_info_with_vidupdate(struct sip_pvt *p); + static int transmit_message(struct sip_pvt *p, int init, int auth); + static int transmit_refer(struct sip_pvt *p, const char *dest); ++static int transmit_refer_with_content(struct sip_pvt *p, const char *type, const char *content); + 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); +@@ -1234,7 +1396,11 @@ + static void copy_request(struct sip_request *dst, const struct sip_request *src); + static void receive_message(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e); + static void parse_moved_contact(struct sip_pvt *p, struct sip_request *req, char **name, char **number, int set_call_forward); +-static int sip_send_mwi_to_peer(struct sip_peer *peer, int cache_only); ++static int sip_send_mwi(struct sip_peer *peer, int cache_only); ++static int sip_send_bulkupdate(struct sip_peer *peer); ++static void extensionstate_subscriptions(struct sip_peer *peer); ++static void register_peer_aliases(struct sip_peer *peer); ++static void expire_peer_aliases(struct sip_peer *peer); + + /* Misc dialog routines */ + static int __sip_autodestruct(const void *data); +@@ -1249,6 +1415,7 @@ + static int build_path(struct sip_pvt *p, struct sip_peer *peer, struct sip_request *req, const char *pathbuf); + static enum check_auth_result register_verify(struct sip_pvt *p, struct ast_sockaddr *addr, + struct sip_request *req, const char *uri); ++struct sip_pvt *get_sip_pvt(const char *callid, const char *totag, const char *fromtag); + static int get_sip_pvt_from_replaces(const char *callid, const char *totag, const char *fromtag, + struct sip_pvt **out_pvt, struct ast_channel **out_chan); + static void check_pendings(struct sip_pvt *p); +@@ -1259,6 +1426,7 @@ + + static int sip_sipredirect(struct sip_pvt *p, const char *dest); + static int is_method_allowed(unsigned int *allowed_methods, enum sipmethod method); ++static void start_record_thread(const char *callid, const char *tag, const char *theirtag, int outgoing); + + /*--- Codec handling / SDP */ + static void try_suggested_sip_codec(struct sip_pvt *p); +@@ -1272,9 +1440,9 @@ + static int process_sdp_a_ice(const char *a, struct sip_pvt *p, struct ast_rtp_instance *instance, int rtcp_mux); + static int process_sdp_a_rtcp_mux(const char *a, struct sip_pvt *p, int *requested); + static int process_sdp_a_dtls(const char *a, struct sip_pvt *p, struct ast_rtp_instance *instance); +-static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newaudiortp, int *last_rtpmap_codec); +-static int process_sdp_a_video(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newvideortp, int *last_rtpmap_codec); +-static int process_sdp_a_text(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newtextrtp, char *red_fmtp, int *red_num_gen, int *red_data_pt, int *last_rtpmap_codec); ++static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newaudiortp, int *audio_codec, int *rtpmap_codecs); ++static int process_sdp_a_video(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newvideortp, int *video_codec, int *rtpmap_codecs); ++static int process_sdp_a_text(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newtextrtp, char *red_fmtp, int *red_num_gen, int *red_data_pt, int *rtpmap_codecs); + static int process_sdp_a_image(const char *a, struct sip_pvt *p); + static void add_ice_to_sdp(struct ast_rtp_instance *instance, struct ast_str **a_buf); + static void add_dtls_to_sdp(struct ast_rtp_instance *instance, struct ast_str **a_buf); +@@ -1316,7 +1484,7 @@ + static int expire_register(const void *data); + static void *do_monitor(void *data); + static int restart_monitor(void); +-static void peer_mailboxes_to_str(struct ast_str **mailbox_str, struct sip_peer *peer); ++static void get_peer_mailboxes(struct ast_str **mailbox_str, struct sip_peer *peer); + static struct ast_variable *copy_vars(struct ast_variable *src); + static int dialog_find_multiple(void *obj, void *arg, int flags); + static struct ast_channel *sip_pvt_lock_full(struct sip_pvt *pvt); +@@ -1338,6 +1506,7 @@ + static void mwi_event_cb(void *, struct stasis_subscription *, struct stasis_message *); + static void network_change_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message); + static void acl_change_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message); ++static void pickup_notify_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message); + static void sip_keepalive_all_peers(void); + #define peer_in_destruction(peer) (ao2_ref(peer, 0) == 0) + +@@ -1402,6 +1571,8 @@ + static inline int sip_debug_test_pvt(struct sip_pvt *p); + static void append_history_full(struct sip_pvt *p, const char *fmt, ...); + static void sip_dump_history(struct sip_pvt *dialog); ++static void parse_rtp_stats(struct sip_pvt *pvt, struct sip_request *req); ++static void send_qrt_url(struct sip_peer *peer); + + /*--- Device object handling */ + static struct sip_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime, int devstate_only); +@@ -1412,7 +1583,7 @@ + static struct sip_peer *temp_peer(const char *name); + static void register_peer_exten(struct sip_peer *peer, int onoff); + static int sip_poke_peer_s(const void *data); +-static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, struct sip_peer *p, struct sip_request *req); ++static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, struct sip_peer *p, struct sip_request *req, int *addrchanged); + static void reg_source_db(struct sip_peer *peer); + static void destroy_association(struct sip_peer *peer); + static void set_insecure_flags(struct ast_flags *flags, const char *value, int lineno); +@@ -1467,9 +1638,14 @@ + static int transmit_state_notify(struct sip_pvt *p, struct state_notify_data *data, int full, int timeout); + static void update_connectedline(struct sip_pvt *p, const void *data, size_t datalen); + static void update_redirecting(struct sip_pvt *p, const void *data, size_t datalen); ++static int sip_send_donotdisturb(struct sip_peer *peer); ++static int sip_send_huntgroup(struct sip_peer *peer); ++static int sip_send_callforward(struct sip_peer *peer); + static int get_domain(const char *str, char *domain, int len); + static void get_realm(struct sip_pvt *p, const struct sip_request *req); +-static char *get_content(struct sip_request *req); ++static char *get_content(struct sip_request *req, int start, int end); ++static int find_boundary(struct sip_request *req, const char *boundary, int start, int *done); ++const char *find_content_type(struct sip_request *req); + + /*-- TCP connection handling ---*/ + static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_session); +@@ -1516,7 +1692,7 @@ + static int handle_incoming(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, int *recount, int *nounlock); + static int handle_request_update(struct sip_pvt *p, struct sip_request *req); + static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, int *recount, const char *e, int *nounlock); +-static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint32_t seqno, int *nounlock); ++static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, const char *e, int *nounlock); + static int handle_request_bye(struct sip_pvt *p, struct sip_request *req); + static int handle_request_register(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *sin, const char *e); + static int handle_request_cancel(struct sip_pvt *p, struct sip_request *req); +@@ -1581,6 +1757,7 @@ + .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER, + .requester = sip_request_call, /* called with chan unlocked */ + .devicestate = sip_devicestate, /* called with chan unlocked (not chan-specific) */ ++ .presencestate = sip_presencestate, /* called with chan unlocked (not chan-specific) */ + .call = sip_call, /* called with chan locked */ + .send_html = sip_sendhtml, + .hangup = sip_hangup, /* called with chan locked */ +@@ -3407,9 +3584,23 @@ + if (dialog->relatedpeer && dialog->relatedpeer->mwipvt == dialog) { + dialog->relatedpeer->mwipvt = dialog_unref(dialog->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt"); + } ++ /* Remove link from peer to subscription for Feature Events */ ++ if (dialog->relatedpeer && dialog->relatedpeer->fepvt == dialog) { ++ dialog->relatedpeer->fepvt = dialog_unref(dialog->relatedpeer->fepvt, "delete ->relatedpeer->fepvt"); ++ } + 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 (dialog->conference) { ++ ao2_ref(dialog->conference, -1); ++ dialog->conference = NULL; ++ } ++ if (dialog->recordoutpvt) { ++ dialog->recordoutpvt = dialog_unref(dialog->recordoutpvt, "delete ->recordoutpvt"); ++ } ++ if (dialog->recordinpvt) { ++ dialog->recordinpvt = dialog_unref(dialog->recordinpvt, "delete ->recordinpvt"); ++ } + + dialog_ref(dialog, "Stop scheduled items for unlink action"); + if (ast_sched_add(sched, 0, __dialog_unlink_sched_items, dialog) < 0) { +@@ -5309,6 +5500,85 @@ + } + } + ++static void destroy_alias(struct sip_alias *alias) ++{ ++ if (alias->peer) { ++ alias->peer->lastms = 0; ++ ++ if (alias->peer->socket.tcptls_session) { ++ ao2_ref(alias->peer->socket.tcptls_session, -1); ++ } else if (alias->peer->socket.ws_session) { ++ ast_websocket_unref(alias->peer->socket.ws_session); ++ } ++ ++ ast_string_field_set(alias->peer, fullcontact, ""); ++ ast_string_field_set(alias->peer, username, ""); ++ ast_string_field_set(alias->peer, useragent, ""); ++ ++ if (!ast_sockaddr_isnull(&alias->peer->addr)) { ++ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", ++ alias->peer->name); ++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); ++ register_peer_exten(alias->peer, FALSE); ++ ++ memset(&alias->peer->addr, 0, sizeof(alias->peer->addr)); ++ } ++ sip_unref_peer(alias->peer, "destroy_alias removing peer ref"); ++ } ++ ast_free(alias->name); ++ ast_free(alias); ++} ++ ++static void clear_peer_aliases(struct sip_peer *peer) ++{ ++ struct sip_alias *alias; ++ ++ while ((alias = AST_LIST_REMOVE_HEAD(&peer->aliases, entry))) ++ destroy_alias(alias); ++} ++ ++/*! Destroy extension state subscription */ ++static void destroy_subscription(struct sip_subscription *subscription) ++{ ++ if (subscription->pvt) { ++ dialog_unlink_all(subscription->pvt); ++ dialog_unref(subscription->pvt, "destroying subscription"); ++ } ++ ast_string_field_free_memory(subscription); ++ ast_free(subscription); ++} ++ ++/* Destroy all peer-related extension state subscriptions */ ++static void clear_peer_subscriptions(struct sip_peer *peer) ++{ ++ struct sip_subscription *subscription; ++ ++ while ((subscription = AST_LIST_REMOVE_HEAD(&peer->subscriptions, entry))) ++ destroy_subscription(subscription); ++} ++ ++static void destroy_callback(struct sip_peer *peer) ++{ ++ ast_extension_state_del(peer->callback->stateid, NULL); ++ sip_unref_peer(peer, "destroy_callback: removing callback ref"); ++ ast_free(peer->callback->exten); ++ ast_free(peer->callback); ++} ++ ++static void destroy_selected(struct sip_selected *selected) ++{ ++ ast_string_field_free_memory(selected); ++ ast_free(selected); ++} ++ ++static void clear_peer_selected(struct sip_peer *peer) ++{ ++ struct sip_selected *selected; ++ ++ while ((selected = AST_LIST_REMOVE_HEAD(&peer->selected, entry))) ++ destroy_selected(selected); ++} ++ + static void sip_destroy_peer_fn(void *peer) + { + sip_destroy_peer(peer); +@@ -5325,6 +5595,9 @@ + * happening right now. + */ + clear_peer_mailboxes(peer); ++ clear_peer_aliases(peer); ++ clear_peer_subscriptions(peer); ++ clear_peer_selected(peer); + + if (peer->outboundproxy) { + ao2_ref(peer->outboundproxy, -1); +@@ -5342,6 +5615,16 @@ + peer->mwipvt = dialog_unref(peer->mwipvt, "unreffing peer->mwipvt"); + } + ++ if (peer->fepvt) { /* We have an active subscription, delete it */ ++ dialog_unlink_all(peer->fepvt); ++ peer->fepvt = dialog_unref(peer->fepvt, "unreffing peer->fepvt"); ++ } ++ ++ if (peer->callback) { ++ destroy_callback(peer); ++ peer->callback = NULL; ++ } ++ + if (peer->chanvars) { + ast_variables_destroy(peer->chanvars); + peer->chanvars = NULL; +@@ -6009,6 +6292,31 @@ + } + } + ++static void copy_pvt_data(struct sip_pvt *to_pvt, struct sip_pvt *from_pvt) ++{ ++ sip_pvt_lock(from_pvt); ++ to_pvt->sa = from_pvt->sa; ++ to_pvt->recv = from_pvt->recv; ++ copy_socket_data(&to_pvt->socket, &from_pvt->socket); ++ ast_copy_flags(&to_pvt->flags[0], &from_pvt->flags[0], SIP_FLAGS_TO_COPY); ++ ast_copy_flags(&to_pvt->flags[1], &from_pvt->flags[1], SIP_PAGE2_FLAGS_TO_COPY); ++ ast_copy_flags(&to_pvt->flags[2], &from_pvt->flags[2], SIP_PAGE3_FLAGS_TO_COPY); ++ ++ /* Recalculate our side, and recalculate Call ID */ ++ ast_sip_ouraddrfor(&to_pvt->sa, &to_pvt->ourip, to_pvt); ++ change_callid_pvt(to_pvt, NULL); ++ ++ ast_string_field_set(to_pvt, tohost, from_pvt->tohost); ++ to_pvt->portinuri = from_pvt->portinuri; ++ to_pvt->fromdomainport = from_pvt->fromdomainport; ++ ++ ast_string_field_set(to_pvt, fullcontact, from_pvt->fullcontact); ++ ast_string_field_set(to_pvt, username, from_pvt->username); ++ ast_string_field_set(to_pvt, fromuser, from_pvt->fromuser); ++ ast_string_field_set(to_pvt, fromname, from_pvt->fromname); ++ sip_pvt_unlock(from_pvt); ++} ++ + /*! \brief Initialize DTLS-SRTP support on an RTP instance */ + static int dialog_initialize_dtls_srtp(const struct sip_pvt *dialog, struct ast_rtp_instance *rtp, struct ast_sdp_srtp **srtp) + { +@@ -6312,6 +6620,10 @@ + dialog->fromdomainport = peer->fromdomainport; + } + dialog->callingpres = peer->callingpres; ++ dialog->donotdisturb = peer->donotdisturb; ++ if (!ast_strlen_zero(peer->callforward)) { ++ ast_string_field_set(dialog, callforward, peer->callforward); ++ } + + return 0; + } +@@ -6480,6 +6792,15 @@ + } + } + ++ if (p->donotdisturb && ast_test_flag(&p->flags[2], SIP_PAGE3_DND_BUSY)) { ++ ast_queue_control(p->owner, AST_CONTROL_BUSY); ++ return 0; ++ } else if (!ast_strlen_zero(p->callforward)) { ++ ast_channel_call_forward_set(ast, p->callforward); ++ ast_queue_control(p->owner, AST_CONTROL_BUSY); ++ return 0; ++ } ++ + /* Check whether there is vxml_url, distinctive ring variables */ + headp = ast_channel_varshead(ast); + AST_LIST_TRAVERSE(headp, current, entries) { +@@ -6715,6 +7036,9 @@ + /* Remove link from peer to subscription of MWI */ + if (p->relatedpeer && p->relatedpeer->mwipvt == p) + p->relatedpeer->mwipvt = dialog_unref(p->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt"); ++ /* Remove link from peer to subscription for Feature Events */ ++ if (p->relatedpeer && p->relatedpeer->fepvt) ++ p->relatedpeer->fepvt = dialog_unref(p->relatedpeer->fepvt, "delete ->relatedpeer->fepvt"); + 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"); + +@@ -7253,6 +7577,11 @@ + if (sipdebug) + ast_debug(1, "update_call_counter(%s) - decrement call limit counter on hangup\n", p->username); + update_call_counter(p, DEC_CALL_LIMIT); ++ if (!ast_test_flag(&p->flags[0], SIP_INC_COUNT) && p->relatedpeer) { ++ ao2_lock(p->relatedpeer); ++ clear_peer_selected(p->relatedpeer); ++ ao2_unlock(p->relatedpeer); ++ } + } + + /* Determine how to disconnect */ +@@ -7319,7 +7648,9 @@ + const char *res; + + stop_provisional_keepalive(p); +- if (p->hangupcause && (res = hangup_cause2sip(p->hangupcause))) ++ if (ast_test_flag(&p->flags[2], SIP_PAGE3_TRANSFER_RESPONSE)) ++ transmit_response_reliable(p, "500 Internal Server Error", &p->initreq); ++ else if (p->hangupcause && (res = hangup_cause2sip(p->hangupcause))) + transmit_response_reliable(p, res, &p->initreq); + else + transmit_response_reliable(p, "603 Declined", &p->initreq); +@@ -8142,6 +8473,7 @@ + case AST_CONTROL_UPDATE_RTP_PEER: /* Absorb this since it is handled by the bridge */ + break; + case AST_CONTROL_FLASH: /* We don't currently handle AST_CONTROL_FLASH here, but it is expected, so we don't need to warn either. */ ++ case AST_CONTROL_MCID: + res = -1; + break; + case AST_CONTROL_PVT_CAUSE_CODE: /* these should be handled by the code in channel.c */ +@@ -8614,14 +8946,12 @@ + return __get_header(req, name, &start); + } + +- + AST_THREADSTORAGE(sip_content_buf); + + /*! \brief Get message body content */ +-static char *get_content(struct sip_request *req) ++static char *get_content(struct sip_request *req, int start, int end) + { + struct ast_str *str; +- int i; + + if (!(str = ast_str_thread_get(&sip_content_buf, 128))) { + return NULL; +@@ -8629,8 +8959,8 @@ + + ast_str_reset(str); + +- for (i = 0; i < req->lines; i++) { +- if (ast_str_append(&str, 0, "%s\n", REQ_OFFSET_TO_STR(req, line[i])) < 0) { ++ while (start < req->lines && start <= end) { ++ if (ast_str_append(&str, 0, "%s\n", REQ_OFFSET_TO_STR(req, line[start++])) < 0) { + return NULL; + } + } +@@ -8638,6 +8968,68 @@ + return ast_str_buffer(str); + } + ++/*! \brief find the content boundary */ ++static int find_boundary(struct sip_request *req, const char *boundary, int start, int *done) ++{ ++ char *line; ++ int len = strlen(boundary); ++ ++ for (*done = 0; start < req->lines; start++) { ++ line = REQ_OFFSET_TO_STR(req, line[start]); ++ if (strncmp(line, "--", 2)) { ++ continue; ++ } ++ if (!strncmp(boundary, line + 2, len)) { ++ if (!strcmp(line + 2 + len, "--")) { ++ *done = 1; ++ } else if (strcmp(line + 2 + len, "")) { ++ continue; ++ } ++ return start; ++ } ++ } ++ ++ return -1; ++} ++ ++/*! \brief get the content type from either the Content-Type header or the Content-Type of the first part */ ++const char *find_content_type(struct sip_request *req) ++{ ++ const char *type = sip_get_header(req, "Content-Type"); ++ char *boundary; ++ const char *line; ++ int start, done; ++ ++ if (ast_strlen_zero(type) || strncasecmp(type, "multipart/mixed", 15)) { ++ return type; ++ } ++ ++ if ((boundary = strcasestr(type, ";boundary="))) { ++ boundary += 10; ++ } else if ((boundary = strcasestr(type, "; boundary="))) { ++ boundary += 11; ++ } else { ++ return ""; ++ } ++ boundary = ast_strdupa(boundary); ++ boundary = strsep(&boundary, ";"); ++ ++ if ((start = find_boundary(req, boundary, 0, &done)) == -1) { ++ return ""; ++ } ++ ++ for (++start; start < req->lines; start++) { ++ line = REQ_OFFSET_TO_STR(req, line[start]); ++ if (!strncasecmp(line, "Content-Type:", 13)) { ++ return ast_skip_blanks(line + 13); ++ } else if (!strcasecmp(line, "")) { ++ break; ++ } ++ } ++ ++ return ""; ++} ++ + /*! \brief Read RTP from network */ + static struct ast_frame *sip_rtp_read(struct ast_channel *ast, struct sip_pvt *p, int *faxdetect) + { +@@ -10320,7 +10712,9 @@ + /* Others */ + int sendonly = -1; + unsigned int numberofports; +- int last_rtpmap_codec = 0; ++ int audio_codec = 255; ++ int video_codec = 255; ++ int rtpmap_codecs = 0; + int red_data_pt[10]; /* For T.140 RED */ + int red_num_gen = 0; /* For T.140 RED */ + char red_fmtp[100] = "empty"; /* For T.140 RED */ +@@ -10388,11 +10782,11 @@ + if (process_sdp_a_sendonly(value, &sendonly)) { + processed = TRUE; + } +- else if (process_sdp_a_audio(value, p, &newaudiortp, &last_rtpmap_codec)) ++ else if (process_sdp_a_audio(value, p, &newaudiortp, &audio_codec, &rtpmap_codecs)) + processed = TRUE; +- else if (process_sdp_a_video(value, p, &newvideortp, &last_rtpmap_codec)) ++ else if (process_sdp_a_video(value, p, &newvideortp, &video_codec, &rtpmap_codecs)) + processed = TRUE; +- else if (process_sdp_a_text(value, p, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &last_rtpmap_codec)) ++ else if (process_sdp_a_text(value, p, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &rtpmap_codecs)) + processed = TRUE; + else if (process_sdp_a_image(value, p)) + processed = TRUE; +@@ -10849,7 +11243,7 @@ + ast_log(AST_LOG_NOTICE, "Processed audio crypto attribute without SAVP specified; accepting anyway\n"); + secure_audio = TRUE; + } +- } else if (process_sdp_a_audio(value, p, &newaudiortp, &last_rtpmap_codec)) { ++ } else if (process_sdp_a_audio(value, p, &newaudiortp, &audio_codec, &rtpmap_codecs)) { + processed = TRUE; + } else if (process_sdp_a_rtcp_mux(value, p, &remote_rtcp_mux_audio)) { + processed = TRUE; +@@ -10872,7 +11266,7 @@ + ast_log(AST_LOG_NOTICE, "Processed video crypto attribute without SAVP specified; accepting anyway\n"); + secure_video = TRUE; + } +- } else if (process_sdp_a_video(value, p, &newvideortp, &last_rtpmap_codec)) { ++ } else if (process_sdp_a_video(value, p, &newvideortp, &video_codec, &rtpmap_codecs)) { + processed = TRUE; + } else if (process_sdp_a_rtcp_mux(value, p, &remote_rtcp_mux_video)) { + processed = TRUE; +@@ -10882,7 +11276,7 @@ + else if (text) { + if (process_sdp_a_ice(value, p, p->trtp, rtcp_mux_offered)) { + processed = TRUE; +- } else if (process_sdp_a_text(value, p, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &last_rtpmap_codec)) { ++ } else if (process_sdp_a_text(value, p, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &rtpmap_codecs)) { + processed = TRUE; + } else if (!processed_crypto && process_crypto(p, p->trtp, &p->tsrtp, value)) { + processed_crypto = TRUE; +@@ -11564,11 +11958,11 @@ + return found; + } + +-static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newaudiortp, int *last_rtpmap_codec) ++static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newaudiortp, int *audio_codec, int *rtpmap_codecs) + { + int found = FALSE; + unsigned int codec; +- char mimeSubtype[128]; ++ char mime_subtype[128]; + char fmtp_string[256]; + unsigned int sample_rate; + int debug = sip_debug_test_pvt(p); +@@ -11591,24 +11985,24 @@ + ast_rtp_codecs_set_framing(newaudiortp, framing); + } + found = TRUE; +- } else if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mimeSubtype, &sample_rate) == 3) { ++ } else if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mime_subtype, &sample_rate) == 3) { + /* We have a rtpmap to handle */ +- if (*last_rtpmap_codec < SDP_MAX_RTPMAP_CODECS) { +- if (!(ast_rtp_codecs_payloads_set_rtpmap_type_rate(newaudiortp, NULL, codec, "audio", mimeSubtype, ++ if (*rtpmap_codecs < SDP_MAX_RTPMAP_CODECS) { ++ if (!(ast_rtp_codecs_payloads_set_rtpmap_type_rate(newaudiortp, NULL, codec, "audio", mime_subtype, + ast_test_flag(&p->flags[0], SIP_G726_NONSTANDARD) ? AST_RTP_OPT_G726_NONSTANDARD : 0, sample_rate))) { + if (debug) +- ast_verbose("Found audio description format %s for ID %u\n", mimeSubtype, codec); +- //found_rtpmap_codecs[last_rtpmap_codec] = codec; +- (*last_rtpmap_codec)++; ++ ast_verbose("Found audio description format %s for ID %u\n", mime_subtype, codec); ++ *audio_codec = codec; ++ (*rtpmap_codecs)++; + found = TRUE; + } else { + ast_rtp_codecs_payloads_unset(newaudiortp, NULL, codec); + if (debug) +- ast_verbose("Found unknown media description format %s for ID %u\n", mimeSubtype, codec); ++ ast_verbose("Found unknown media description format %s for ID %u\n", mime_subtype, codec); + } + } else { + if (debug) +- ast_verbose("Discarded description format %s for ID %u\n", mimeSubtype, codec); ++ ast_verbose("Discarded description format %s for ID %u\n", mime_subtype, codec); + } + } else if (sscanf(a, "fmtp: %30u %255[^\t\n]", &codec, fmtp_string) == 2) { + struct ast_format *format; +@@ -11644,36 +12038,37 @@ + return found; + } + +-static int process_sdp_a_video(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newvideortp, int *last_rtpmap_codec) ++static int process_sdp_a_video(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newvideortp, int *video_codec, int *rtpmap_codecs) + { + int found = FALSE; + unsigned int codec; +- char mimeSubtype[128]; ++ char mime_subtype[128]; + unsigned int sample_rate; + int debug = sip_debug_test_pvt(p); + char fmtp_string[256]; ++ char imageattr[256]; + +- if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mimeSubtype, &sample_rate) == 3) { ++ if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mime_subtype, &sample_rate) == 3) { + /* We have a rtpmap to handle */ +- if (*last_rtpmap_codec < SDP_MAX_RTPMAP_CODECS) { ++ if (*rtpmap_codecs < SDP_MAX_RTPMAP_CODECS) { + /* Note: should really look at the '#chans' params too */ +- if (!strncasecmp(mimeSubtype, "H26", 3) || !strncasecmp(mimeSubtype, "MP4", 3) +- || !strncasecmp(mimeSubtype, "VP8", 3)) { +- if (!(ast_rtp_codecs_payloads_set_rtpmap_type_rate(newvideortp, NULL, codec, "video", mimeSubtype, 0, sample_rate))) { ++ if (!strncasecmp(mime_subtype, "H26", 3) || !strncasecmp(mime_subtype, "MP4", 3) ++ || !strncasecmp(mime_subtype, "VP8", 3)) { ++ if (!(ast_rtp_codecs_payloads_set_rtpmap_type_rate(newvideortp, NULL, codec, "video", mime_subtype, 0, sample_rate))) { + if (debug) +- ast_verbose("Found video description format %s for ID %u\n", mimeSubtype, codec); +- //found_rtpmap_codecs[last_rtpmap_codec] = codec; +- (*last_rtpmap_codec)++; ++ ast_verbose("Found video description format %s for ID %u\n", mime_subtype, codec); ++ *video_codec = codec; ++ (*rtpmap_codecs)++; + found = TRUE; + } else { + ast_rtp_codecs_payloads_unset(newvideortp, NULL, codec); + if (debug) +- ast_verbose("Found unknown media description format %s for ID %u\n", mimeSubtype, codec); ++ ast_verbose("Found unknown media description format %s for ID %u\n", mime_subtype, codec); + } + } + } else { + if (debug) +- ast_verbose("Discarded description format %s for ID %u\n", mimeSubtype, codec); ++ ast_verbose("Discarded description format %s for ID %u\n", mime_subtype, codec); + } + } else if (sscanf(a, "fmtp: %30u %255[^\t\n]", &codec, fmtp_string) == 2) { + struct ast_format *format; +@@ -11693,41 +12088,61 @@ + } + ao2_ref(format, -1); + } ++ } else if (sscanf(a, "imageattr: %30u recv %255[^\t\n]", &codec, imageattr) == 2 ++ || (codec = *video_codec, sscanf(a, "imageattr:* recv %255[^\t\n]", imageattr) == 1)) { ++ struct ast_format *format; ++ ++ if ((format = ast_rtp_codecs_get_payload_format(newvideortp, codec))) { ++ struct ast_format *format_parsed; ++ ++ format_parsed = ast_format_attribute_set(format, "imageattr", imageattr); ++ ++ if (format_parsed) { ++ ast_rtp_codecs_payload_replace_format(newvideortp, codec, format_parsed); ++ ao2_replace(format, format_parsed); ++ ao2_ref(format_parsed, -1); ++ found = TRUE; ++ } else { ++ ast_rtp_codecs_payloads_unset(newvideortp, NULL, codec); ++ } ++ ao2_ref(format, -1); ++ } + } + + return found; + } + +-static int process_sdp_a_text(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newtextrtp, char *red_fmtp, int *red_num_gen, int *red_data_pt, int *last_rtpmap_codec) ++static int process_sdp_a_text(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newtextrtp, char *red_fmtp, int *red_num_gen, int *red_data_pt, int *rtpmap_codecs) + { + int found = FALSE; + unsigned int codec; +- char mimeSubtype[128]; ++ char mime_subtype[128]; + unsigned int sample_rate; + char *red_cp; + int debug = sip_debug_test_pvt(p); + +- if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mimeSubtype, &sample_rate) == 3) { ++ if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mime_subtype, &sample_rate) == 3) { + /* We have a rtpmap to handle */ +- if (*last_rtpmap_codec < SDP_MAX_RTPMAP_CODECS) { +- if (!strncasecmp(mimeSubtype, "T140", 4)) { /* Text */ ++ if (*rtpmap_codecs < SDP_MAX_RTPMAP_CODECS) { ++ if (!strncasecmp(mime_subtype, "T140", 4)) { /* Text */ + if (p->trtp) { +- /* ast_verbose("Adding t140 mimeSubtype to textrtp struct\n"); */ +- ast_rtp_codecs_payloads_set_rtpmap_type_rate(newtextrtp, NULL, codec, "text", mimeSubtype, 0, sample_rate); ++ /* ast_verbose("Adding t140 mime_subtype to textrtp struct\n"); */ ++ ast_rtp_codecs_payloads_set_rtpmap_type_rate(newtextrtp, NULL, codec, "text", mime_subtype, 0, sample_rate); + found = TRUE; + } +- } else if (!strncasecmp(mimeSubtype, "RED", 3)) { /* Text with Redudancy */ ++ } else if (!strncasecmp(mime_subtype, "RED", 3)) { /* Text with Redudancy */ + if (p->trtp) { +- ast_rtp_codecs_payloads_set_rtpmap_type_rate(newtextrtp, NULL, codec, "text", mimeSubtype, 0, sample_rate); ++ ast_rtp_codecs_payloads_set_rtpmap_type_rate(newtextrtp, NULL, codec, "text", mime_subtype, 0, sample_rate); + sprintf(red_fmtp, "fmtp:%u ", codec); + if (debug) + ast_verbose("RED submimetype has payload type: %u\n", codec); + found = TRUE; + } + } ++ (*rtpmap_codecs)++; + } else { + if (debug) +- ast_verbose("Discarded description format %s for ID %u\n", mimeSubtype, codec); ++ ast_verbose("Discarded description format %s for ID %u\n", mime_subtype, codec); + } + } else if (!strncmp(a, red_fmtp, strlen(red_fmtp))) { + char *rest = NULL; +@@ -11868,15 +12283,20 @@ + * is supported for this dialog. */ + static int add_supported(struct sip_pvt *pvt, struct sip_request *req) + { +- char supported_value[SIPBUFSIZE]; +- int res; ++ struct ast_str *supported = ast_str_alloca(128); + +- sprintf(supported_value, "replaces%s%s", +- (st_get_mode(pvt, 0) != SESSION_TIMER_MODE_REFUSE) ? ", timer" : "", +- ast_test_flag(&pvt->flags[0], SIP_USEPATH) ? ", path" : ""); +- res = add_header(req, "Supported", supported_value); ++ ast_str_append(&supported, -1, "replaces"); ++ if (st_get_mode(pvt, 0) != SESSION_TIMER_MODE_REFUSE) { ++ ast_str_append(&supported, -1, ",timer"); ++ } ++ if (ast_test_flag(&pvt->flags[0], SIP_USEPATH)) { ++ ast_str_append(&supported, -1, ",path"); ++ } ++ if (ast_test_flag(&pvt->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ ast_str_append(&supported, -1, ",X-cisco-sis-10.0.0"); ++ } + +- return res; ++ return add_header(req, "Supported", ast_str_buffer(supported)); + } + + /*! \brief Add header to SIP message */ +@@ -12537,8 +12957,11 @@ + ast_clear_flag(&p->flags[1], SIP_PAGE2_CONNECTLINEUPDATE_PEND); + add_rpid(&resp, p); + } ++ if (p->method == SIP_INVITE) { ++ add_remotecc_call_info(&resp, p); ++ } + if (ast_test_flag(&p->flags[0], SIP_OFFER_CC)) { +- add_cc_call_info_to_response(p, &resp); ++ add_cc_call_info(&resp, p); + } + + /* If we are sending a 302 Redirect we can add a diversion header if the redirect information is set */ +@@ -12785,6 +13208,36 @@ + return send_response(p, &resp, reliable, seqno); + } + ++/*! \brief Respond with an optionind response */ ++static int transmit_response_with_optionsind(struct sip_pvt *p, const struct sip_request *req) ++{ ++ struct sip_request resp; ++ ++ respprep(&resp, p, "200 OK", req); ++ ++ add_header(&resp, "Content-Type", "application/x-cisco-remotecc-response+xml"); ++ add_date(&resp); ++ ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "200\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ ++ return send_response(p, &resp, XMIT_UNRELIABLE, 0); ++} ++ + /*! + \brief Extract domain from SIP To/From header + \retval -1 on error. +@@ -13007,15 +13460,15 @@ + { + struct ast_str *tmp = ast_str_alloca(256); + char tmp2[256]; +- char lid_name_buf[128]; + char *lid_num; + char *lid_name; + int lid_pres; ++ int lid_source; + const char *fromdomain; +- const char *privacy = NULL; +- const char *screen = NULL; ++ const char *privacy; ++ const char *screen; ++ const char *callback_num; + struct ast_party_id connected_id; +- const char *anonymous_string = "\"Anonymous\" "; + + if (!ast_test_flag(&p->flags[0], SIP_SENDRPID)) { + return 0; +@@ -13024,18 +13477,16 @@ + if (!p->owner) { + return 0; + } ++ ++ lid_source = ast_channel_connected(p->owner)->source; + connected_id = ast_channel_connected_effective_id(p->owner); + lid_num = S_COR(connected_id.number.valid, connected_id.number.str, NULL); +- if (!lid_num) { +- return 0; +- } + lid_name = S_COR(connected_id.name.valid, connected_id.name.str, NULL); +- if (!lid_name) { +- lid_name = lid_num; ++ if (!lid_num && !lid_name) { ++ return 0; + } +- ast_escape_quoted(lid_name, lid_name_buf, sizeof(lid_name_buf)); +- lid_pres = ast_party_id_presentation(&connected_id); + ++ lid_pres = ast_party_id_presentation(&connected_id); + if (((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) && + (ast_test_flag(&p->flags[1], SIP_PAGE2_TRUST_ID_OUTBOUND) == SIP_PAGE2_TRUST_ID_OUTBOUND_NO)) { + /* If pres is not allowed and we don't trust the peer, we don't apply an RPID header */ +@@ -13051,66 +13502,63 @@ + fromdomain = ast_sockaddr_stringify_host_remote(&p->ourip); + } + +- lid_num = ast_uri_encode(lid_num, tmp2, sizeof(tmp2), ast_uri_sip_user); ++ if (!ast_strlen_zero(lid_name)) { ++ if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ if (!strcmp(lid_name, "Conference") && lid_source == AST_CONNECTED_LINE_UPDATE_SOURCE_CONFERENCE) { ++ /* Phone will translate \2004 into the localised version of Conference and enable ConfList/ConfDetails support */ ++ lid_name = "\2004"; ++ } else if (!strcmp(lid_name, "Park") && lid_source == AST_CONNECTED_LINE_UPDATE_SOURCE_PARKED_CALL) { ++ lid_name = "\2005"; ++ } ++ } ++ ++ ast_escape_quoted(lid_name, tmp2, sizeof(tmp2)); ++ ast_str_append(&tmp, -1, "\"%s\" ", tmp2); ++ } ++ ast_str_append(&tmp, -1, "owner, "CISCO_CALLBACK_NUMBER"); ++ if (!ast_strlen_zero(callback_num)) { ++ ast_uri_encode(callback_num, tmp2, sizeof(tmp2), ast_uri_sip_user); ++ ast_str_append(&tmp, -1, ";x-cisco-callback-number=%s", tmp2); ++ } ++ ast_str_append(&tmp, -1, ">"); + + if (ast_test_flag(&p->flags[0], SIP_SENDRPID_PAI)) { + if (ast_test_flag(&p->flags[1], SIP_PAGE2_TRUST_ID_OUTBOUND) != SIP_PAGE2_TRUST_ID_OUTBOUND_LEGACY) { + /* trust_id_outbound = yes - Always give full information even if it's private, but append a privacy header + * When private data is included */ +- ast_str_set(&tmp, -1, "\"%s\" ", lid_name_buf, lid_num, fromdomain); + if ((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { + add_header(req, "Privacy", "id"); + } + } else { + /* trust_id_outbound = legacy - behave in a non RFC-3325 compliant manner and send anonymized data when + * when handling private data. */ +- if ((lid_pres & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED) { +- ast_str_set(&tmp, -1, "\"%s\" ", lid_name_buf, lid_num, fromdomain); +- } else { +- ast_str_set(&tmp, -1, "%s", anonymous_string); ++ if ((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { ++ ast_str_set(&tmp, -1, "\"Anonymous\" "); + } + } + add_header(req, "P-Asserted-Identity", ast_str_buffer(tmp)); + } else { +- ast_str_set(&tmp, -1, "\"%s\" ;party=%s", lid_name_buf, lid_num, fromdomain, p->outgoing_call ? "calling" : "called"); ++ ast_str_append(&tmp, -1, ";party=%s", p->outgoing_call ? "calling" : "called"); + +- switch (lid_pres) { +- case AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED: +- case AST_PRES_ALLOWED_USER_NUMBER_FAILED_SCREEN: +- privacy = "off"; +- screen = "no"; +- break; +- case AST_PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN: +- case AST_PRES_ALLOWED_NETWORK_NUMBER: +- privacy = "off"; +- screen = "yes"; +- break; +- case AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED: +- case AST_PRES_PROHIB_USER_NUMBER_FAILED_SCREEN: +- privacy = "full"; +- screen = "no"; +- break; +- case AST_PRES_PROHIB_USER_NUMBER_PASSED_SCREEN: +- case AST_PRES_PROHIB_NETWORK_NUMBER: ++ if ((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { + privacy = "full"; +- screen = "yes"; +- break; +- case AST_PRES_NUMBER_NOT_AVAILABLE: +- break; +- default: +- if ((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { +- privacy = "full"; +- } +- else +- privacy = "off"; +- screen = "no"; +- break; ++ } else { ++ privacy = "off"; + } + +- if (!ast_strlen_zero(privacy) && !ast_strlen_zero(screen)) { +- ast_str_append(&tmp, -1, ";privacy=%s;screen=%s", privacy, screen); ++ if (lid_pres & AST_PRES_USER_NUMBER_PASSED_SCREEN) { ++ screen = "yes"; ++ } else { ++ screen = "no"; + } + ++ ast_str_append(&tmp, -1, ";privacy=%s;screen=%s", privacy, screen); + add_header(req, "Remote-Party-ID", ast_str_buffer(tmp)); + } + return 0; +@@ -13331,6 +13779,12 @@ + } + + ast_str_append(m_buf, 0, " %d", rtp_code); ++ ++ if (ast_format_cmp(format, ast_format_h264) == AST_FORMAT_CMP_EQUAL && ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ /* Needs to be a large number otherwise video quality is poor */ ++ ast_str_append(a_buf, 0, "b=TIAS:4000000\r\n"); ++ } ++ + ast_str_append(a_buf, 0, "a=rtpmap:%d %s/%u\r\n", rtp_code, subtype, rate); + /* VP8: add RTCP FIR support */ + if (ast_format_cmp(format, ast_format_vp8) == AST_FORMAT_CMP_EQUAL) { +@@ -13338,6 +13792,16 @@ + } + + ast_format_generate_sdp_fmtp(format, rtp_code, a_buf); ++ ++ if (ast_format_cmp(format, ast_format_h264) == AST_FORMAT_CMP_EQUAL && ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ const char *imageattr = (const char *) ast_format_attribute_get(format, "imageattr"); ++ ++ if (ast_strlen_zero(imageattr)) { ++ imageattr = "[x=640,y=480,q=0.50]"; ++ } ++ ++ ast_str_append(a_buf, 0, "a=imageattr:%d recv %s\r\n", rtp_code, imageattr); ++ } + } + + /*! \brief Add text codec offer to SDP offer/answer body in INVITE or 200 OK */ +@@ -13711,7 +14175,7 @@ + ast_test_flag(&p->flags[2], SIP_PAGE3_FORCE_AVP))); + + /* Build max bitrate string */ +- if (p->maxcallbitrate) ++ if (p->maxcallbitrate && !ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) + snprintf(bandwidth, sizeof(bandwidth), "b=CT:%d\r\n", p->maxcallbitrate); + if (debug) { + ast_verbose("Video is at %s\n", ast_sockaddr_stringify(&vdest)); +@@ -13751,6 +14215,11 @@ + } + + /* Start building generic SDP headers */ ++ if (ast_test_flag(&p->flags[2], SIP_PAGE3_RELAY_NEAREND)) { ++ ast_str_append(&a_audio, 0, "a=label:X-relay-nearend\r\n"); ++ } else if (ast_test_flag(&p->flags[2], SIP_PAGE3_RELAY_FAREND)) { ++ ast_str_append(&a_audio, 0, "a=label:X-relay-farend\r\n"); ++ } + + /* We break with the "recommendation" and send our IP, in order that our + peer doesn't have to ast_gethostbyname() us */ +@@ -14097,7 +14566,7 @@ + } + } + +-static void add_cc_call_info_to_response(struct sip_pvt *p, struct sip_request *resp) ++static void add_cc_call_info(struct sip_request *resp, struct sip_pvt *p) + { + char uri[SIPBUFSIZE]; + struct ast_str *header = ast_str_alloca(SIPBUFSIZE); +@@ -14129,6 +14598,52 @@ + ao2_ref(agent, -1); + } + ++static void add_remotecc_call_info(struct sip_request *req, struct sip_pvt *p) ++{ ++ struct ast_str *header = ast_str_alloca(SIPBUFSIZE); ++ ++ if (!ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ return; ++ } ++ ++ ast_str_set(&header, 0, "; orientation=%s; security=", ++ p->outgoing_call ? "from" : "to"); ++ if (p->socket.type & AST_TRANSPORT_TLS) { ++ if (p->srtp) { ++ ast_str_append(&header, 0, "Encrypted"); ++ } else { ++ ast_str_append(&header, 0, "Authenticated"); ++ } ++ } else { ++ ast_str_append(&header, 0, "NotAuthenticated"); ++ } ++ ++ if (ast_test_flag(&p->flags[0], SIP_OUTGOING) && p->owner) { ++ const char *huntpilot = pbx_builtin_getvar_helper(p->owner, "CISCO_HUNTPILOT"); ++ ++ if (!ast_strlen_zero(huntpilot)) { ++ char *huntpilot_copy = ast_strdupa(huntpilot); ++ char *name, *location, tmp[256]; ++ ++ if (!ast_callerid_parse(huntpilot_copy, &name, &location)) { ++ ast_str_append(&header, 0, "; huntpiloturi=\""); ++ ++ if (!ast_strlen_zero(name)) { ++ ast_uri_encode(name, tmp, sizeof(tmp), ast_uri_sip_user); ++ ast_str_append(&header, 0, "%%%02X%s%%%02X ", ++ (unsigned char) '"', tmp, (unsigned char) '"'); ++ } ++ ++ ast_uri_encode(location, tmp, sizeof(tmp), ast_uri_sip_user); ++ ast_str_append(&header, 0, "\"", ++ tmp, ast_sockaddr_stringify_host_remote(&p->ourip)); ++ } ++ } ++ } ++ ++ add_header(req, "Call-Info", ast_str_buffer(header)); ++} ++ + /*! \brief Used for 200 OK and 183 early media + \retval XMIT_ERROR for network errors. + */ +@@ -14143,9 +14658,10 @@ + respprep(&resp, p, msg, req); + if (rpid == TRUE) { + add_rpid(&resp, p); ++ add_remotecc_call_info(&resp, p); + } + if (ast_test_flag(&p->flags[0], SIP_OFFER_CC)) { +- add_cc_call_info_to_response(p, &resp); ++ add_cc_call_info(&resp, p); + } + if (p->rtp) { + ast_rtp_instance_activate(p->rtp); +@@ -14430,8 +14946,6 @@ + const char *d = NULL; /* domain in from header */ + const char *urioptions = ""; + int ourport; +- int cid_has_name = 1; +- int cid_has_num = 1; + struct ast_party_id connected_id; + int ret; + +@@ -14481,7 +14995,7 @@ + + /* Hey, it's a NOTIFY! See if they've configured a mwi_from. + * XXX Right now, this logic works because the only place that mwi_from +- * is set on the sip_pvt is in sip_send_mwi_to_peer. If things changed, then ++ * is set on the sip_pvt is in sip_send_mwi. If things changed, then + * we might end up putting the mwi_from setting into other types of NOTIFY + * messages as well. + */ +@@ -14490,13 +15004,8 @@ + } + + if (ast_strlen_zero(l)) { +- cid_has_num = 0; + l = default_callerid; + } +- if (ast_strlen_zero(n)) { +- cid_has_name = 0; +- n = l; +- } + + /* Allow user to be overridden */ + if (!ast_strlen_zero(p->fromuser)) +@@ -14539,7 +15048,7 @@ + } + + /* If a caller id name was specified, prefix a display name, if there is enough room. */ +- if (cid_has_name || !cid_has_num) { ++ if (!ast_strlen_zero(n)) { + size_t written = ast_str_strlen(from); + size_t name_len; + if (sip_cfg.pedanticsipchecking) { +@@ -14715,9 +15224,45 @@ + quote_str, reason, quote_str); + } + ++ if (ast_test_flag(&pvt->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ int presentation = ast_party_id_presentation(&diverting_from); ++ int header_len = strlen(header_text); ++ const char *privacy; ++ const char *screen; ++ ++ if ((presentation & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { ++ privacy = "full"; ++ } else { ++ privacy = "off"; ++ } ++ ++ if (presentation & AST_PRES_USER_NUMBER_PASSED_SCREEN) { ++ screen = "yes"; ++ } else { ++ screen = "no"; ++ } ++ ++ snprintf(header_text + header_len, sizeof(header_text) - header_len, ++ ";privacy=%s;screen=%s", privacy, screen); ++ } ++ + add_header(req, "Diversion", header_text); + } + ++static void add_join(struct sip_request *req, struct sip_pvt *pvt) ++{ ++ char tmp[256]; ++ ++ if (!ast_test_flag(&pvt->flags[2], SIP_PAGE3_RELAY_NEAREND) && !ast_test_flag(&pvt->flags[2], SIP_PAGE3_RELAY_FAREND)) ++ return; ++ ++ if (ast_strlen_zero(pvt->join_callid) || ast_strlen_zero(pvt->join_tag) || ast_strlen_zero(pvt->join_theirtag)) ++ return; ++ ++ snprintf(tmp, sizeof(tmp), "%s;from-tag=%s;to-tag=%s", pvt->join_callid, pvt->join_tag, pvt->join_theirtag); ++ add_header(req, "Join", tmp); ++} ++ + 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; +@@ -14784,12 +15329,25 @@ + } + add_date(&req); + if (sipmethod == SIP_REFER && p->refer) { /* Call transfer */ ++ if (!ast_strlen_zero(p->refer->require)) { ++ add_header(&req, "Require", p->refer->require); ++ } + if (!ast_strlen_zero(p->refer->refer_to)) { + add_header(&req, "Refer-To", p->refer->refer_to); + } + if (!ast_strlen_zero(p->refer->referred_by)) { + add_header(&req, "Referred-By", p->refer->referred_by); + } ++ if (!ast_strlen_zero(p->refer->content_id)) { ++ add_header(&req, "Content-Id", p->refer->content_id); ++ } ++ if (!ast_strlen_zero(p->refer->content_type)) { ++ add_header(&req, "Content-Type", p->refer->content_type); ++ } ++ if (ast_str_strlen(p->refer->content)) { ++ add_content(&req, ast_str_buffer(p->refer->content)); ++ } ++ add_expires(&req, p->expiry); + } else if (sipmethod == SIP_SUBSCRIBE) { + if (p->subscribed == MWI_NOTIFICATION) { + add_header(&req, "Event", "message-summary"); +@@ -14881,6 +15439,8 @@ + add_rpid(&req, p); + if (sipmethod == SIP_INVITE) { + add_diversion(&req, p); ++ add_join(&req, p); ++ add_remotecc_call_info(&req, p); + } + if (sdp) { + offered_media_list_destroy(p); +@@ -15175,6 +15735,9 @@ + struct ast_channel *c = NULL; + struct timeval tv = {0,}; + ++ if (!device_state_info) ++ return NULL; ++ + /* iterate ringing devices and get the oldest of all causing channels */ + citer = ao2_iterator_init(device_state_info, 0); + for (; (device_state = ao2_iterator_next(&citer)); ao2_ref(device_state, -1)) { +@@ -15193,93 +15756,9 @@ + return c ? ast_channel_ref(c) : NULL; + } + +-/* XXX Candidate for moving into its own file */ +-static int allow_notify_user_presence(struct sip_pvt *p) +-{ +- return (strstr(p->useragent, "Digium")) ? 1 : 0; +-} +- + /*! \brief Builds XML portion of NOTIFY messages for presence or dialog updates */ + static void state_notify_build_xml(struct state_notify_data *data, int full, const char *exten, const char *context, struct ast_str **tmp, struct sip_pvt *p, int subscribed, const char *mfrom, const char *mto) + { +- enum state { NOTIFY_OPEN, NOTIFY_INUSE, NOTIFY_CLOSED } local_state = NOTIFY_OPEN; +- const char *statestring = "terminated"; +- const char *pidfstate = "--"; +- const char *pidfnote ="Ready"; +- char hint[AST_MAX_EXTENSION]; +- +- switch (data->state) { +- case (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE): +- statestring = (sip_cfg.notifyringing == NOTIFYRINGING_ENABLED) ? "early" : "confirmed"; +- local_state = NOTIFY_INUSE; +- pidfstate = "busy"; +- pidfnote = "Ringing"; +- break; +- case AST_EXTENSION_RINGING: +- statestring = "early"; +- local_state = NOTIFY_INUSE; +- pidfstate = "busy"; +- pidfnote = "Ringing"; +- break; +- case AST_EXTENSION_INUSE: +- statestring = "confirmed"; +- local_state = NOTIFY_INUSE; +- pidfstate = "busy"; +- pidfnote = "On the phone"; +- break; +- case AST_EXTENSION_BUSY: +- statestring = "confirmed"; +- local_state = NOTIFY_CLOSED; +- pidfstate = "busy"; +- pidfnote = "On the phone"; +- break; +- case AST_EXTENSION_UNAVAILABLE: +- statestring = "terminated"; +- local_state = NOTIFY_CLOSED; +- pidfstate = "away"; +- pidfnote = "Unavailable"; +- break; +- case AST_EXTENSION_ONHOLD: +- statestring = "confirmed"; +- local_state = NOTIFY_CLOSED; +- pidfstate = "busy"; +- pidfnote = "On hold"; +- break; +- case AST_EXTENSION_NOT_INUSE: +- default: +- /* Default setting */ +- break; +- } +- +- /* Check which device/devices we are watching and if they are registered */ +- if (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, context, exten)) { +- char *hint2; +- char *individual_hint = NULL; +- int hint_count = 0, unavailable_count = 0; +- +- /* strip off any possible PRESENCE providers from hint */ +- if ((hint2 = strrchr(hint, ','))) { +- *hint2 = '\0'; +- } +- hint2 = hint; +- +- while ((individual_hint = strsep(&hint2, "&"))) { +- hint_count++; +- +- if (ast_device_state(individual_hint) == AST_DEVICE_UNAVAILABLE) +- unavailable_count++; +- } +- +- /* If none of the hinted devices are registered, we will +- * override notification and show no availability. +- */ +- if (hint_count > 0 && hint_count == unavailable_count) { +- local_state = NOTIFY_CLOSED; +- pidfstate = "away"; +- pidfnote = "Not online"; +- } +- } +- + switch (subscribed) { + case XPIDF_XML: + case CPIM_PIDF_XML: +@@ -15290,46 +15769,92 @@ + ast_str_append(tmp, 0, "\n", mfrom); + ast_str_append(tmp, 0, "\n", exten); + ast_str_append(tmp, 0, "
\n", mto); +- ast_str_append(tmp, 0, "\n", (local_state == NOTIFY_OPEN) ? "open" : (local_state == NOTIFY_INUSE) ? "inuse" : "closed"); +- ast_str_append(tmp, 0, "\n", (local_state == NOTIFY_OPEN) ? "online" : (local_state == NOTIFY_INUSE) ? "onthephone" : "offline"); ++ if (data->state & (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE) || data->presence_state == AST_PRESENCE_DND) { ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, "\n"); ++ } else if (data->state & (AST_EXTENSION_BUSY | AST_EXTENSION_UNAVAILABLE | AST_EXTENSION_ONHOLD)) { ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, "\n"); ++ } else { ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, "\n"); ++ } + ast_str_append(tmp, 0, "
\n
\n\n"); + break; + case PIDF_XML: /* Eyebeam supports this format */ +- ast_str_append(tmp, 0, +- "\n" +- "\n", mfrom); +- ast_str_append(tmp, 0, "\n"); +- if (pidfstate[0] != '-') { +- ast_str_append(tmp, 0, "\n", pidfstate); +- } +- ast_str_append(tmp, 0, "\n"); +- ast_str_append(tmp, 0, "%s\n", pidfnote); /* Note */ +- ast_str_append(tmp, 0, "\n", exten); /* Tuple start */ +- ast_str_append(tmp, 0, "%s\n", mto); +- if (pidfstate[0] == 'b') /* Busy? Still open ... */ +- ast_str_append(tmp, 0, "open\n"); +- else +- ast_str_append(tmp, 0, "%s\n", (local_state != NOTIFY_CLOSED) ? "open" : "closed"); +- +- if (allow_notify_user_presence(p) && (data->presence_state != AST_PRESENCE_INVALID) +- && (data->presence_state != AST_PRESENCE_NOT_SET)) { ++ if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, ++ "\n", mfrom); ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, "\n"); ++ if (data->state & AST_EXTENSION_RINGING && sip_cfg.notifyringing) { ++ ast_str_append(tmp, 0, "\n"); ++ } else if (data->state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY | AST_EXTENSION_ONHOLD)) { ++ ast_str_append(tmp, 0, "\n"); ++ } else if (data->presence_state == AST_PRESENCE_DND) { ++ ast_str_append(tmp, 0, "\n"); ++ } ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, "\n", p->dialogver); ++ if (data->state == AST_EXTENSION_UNAVAILABLE) { ++ ast_str_append(tmp, 0, "closed\n"); ++ } else { ++ ast_str_append(tmp, 0, "open\n"); ++ } + ast_str_append(tmp, 0, "\n"); +- ast_str_append(tmp, 0, "\n"); +- ast_str_append(tmp, 0, "\n"); +- ast_str_append(tmp, 0, "%s\n", +- ast_presence_state2str(data->presence_state), +- S_OR(data->presence_subtype, ""), +- S_OR(data->presence_message, "")); +- ast_str_append(tmp, 0, "\n"); +- ast_test_suite_event_notify("DIGIUM_PRESENCE_SENT", ++ ast_str_append(tmp, 0, "\n"); ++ } else { ++ ast_str_append(tmp, 0, ++ "\n" ++ "\n", mfrom); ++ ast_str_append(tmp, 0, "\n"); ++ if (data->state == AST_EXTENSION_UNAVAILABLE) { ++ ast_str_append(tmp, 0, "\n"); ++ } else if (data->state & (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE | AST_EXTENSION_BUSY | AST_EXTENSION_ONHOLD) || data->presence_state == AST_PRESENCE_DND) { ++ ast_str_append(tmp, 0, "\n"); ++ } ++ ast_str_append(tmp, 0, "\n"); ++ if (data->state & AST_EXTENSION_RINGING) { ++ ast_str_append(tmp, 0, "Ringing\n"); ++ } else if (data->state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY)) { ++ ast_str_append(tmp, 0, "On the phone\n"); ++ } else if (data->state == AST_EXTENSION_ONHOLD) { ++ ast_str_append(tmp, 0, "On hold\n"); ++ } else if (data->state == AST_EXTENSION_UNAVAILABLE) { ++ ast_str_append(tmp, 0, "Unavailable\n"); ++ } else if (data->presence_state == AST_PRESENCE_DND) { ++ ast_str_append(tmp, 0, "Do not disturb\n"); ++ } else { ++ ast_str_append(tmp, 0, "Ready\n"); ++ } ++ ast_str_append(tmp, 0, "\n", exten); /* Tuple start */ ++ ast_str_append(tmp, 0, "%s\n", mto); ++ if (data->state & (AST_EXTENSION_UNAVAILABLE | AST_EXTENSION_BUSY | AST_EXTENSION_ONHOLD)) { ++ ast_str_append(tmp, 0, "closed\n"); ++ } else { ++ ast_str_append(tmp, 0, "open\n"); ++ } ++ if (ast_test_flag(&p->flags[2], SIP_PAGE3_WHY_DIDNT_DIGIUM_THINK_OF_THIS) && (data->presence_state > 0)) { ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, "%s\n", ++ ast_presence_state2str(data->presence_state), ++ S_OR(data->presence_subtype, ""), ++ S_OR(data->presence_message, "")); ++ ast_str_append(tmp, 0, "\n"); ++ ast_test_suite_event_notify("DIGIUM_PRESENCE_SENT", + "PresenceState: %s\r\n" + "Subtype: %s\r\n" + "Message: %s", + ast_presence_state2str(data->presence_state), + S_OR(data->presence_subtype, ""), + S_OR(data->presence_message, "")); ++ } ++ ast_str_append(tmp, 0, "\n\n"); + } +- ast_str_append(tmp, 0, "\n\n"); + break; + case DIALOG_INFO_XML: /* SNOM subscribes in this format */ + ast_str_append(tmp, 0, "\n"); +@@ -15439,7 +15964,13 @@ + } else { + ast_str_append(tmp, 0, "\n", exten); + } +- ast_str_append(tmp, 0, "%s\n", statestring); ++ if (data->state & AST_EXTENSION_RINGING && sip_cfg.notifyringing) { ++ ast_str_append(tmp, 0, "early\n"); ++ } else if (data->state & (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE | AST_EXTENSION_BUSY | AST_EXTENSION_ONHOLD) || data->presence_state == AST_PRESENCE_DND) { ++ ast_str_append(tmp, 0, "confirmed\n"); ++ } else { ++ ast_str_append(tmp, 0, "terminated\n"); ++ } + if (data->state == AST_EXTENSION_ONHOLD) { + ast_str_append(tmp, 0, "\n\n" + "\n" +@@ -15534,36 +16065,22 @@ + add_header(&req, "Subscription-State", "terminated;reason=noresource"); + break; + default: +- if (p->expiry) ++ if (p->expiry > 0) { ++ char tmp[64]; ++ ++ snprintf(tmp, sizeof(tmp), "active;expires=%d", p->expiry); + add_header(&req, "Subscription-State", "active"); +- else /* Expired */ ++ } else if (ast_test_flag(&p->flags[2], SIP_PAGE3_SUBSCRIPTIONSTATE_ACTIVE)) { ++ add_header(&req, "Subscription-State", "active"); ++ } else { /* Expired */ + add_header(&req, "Subscription-State", "terminated;reason=timeout"); ++ } + } + +- switch (p->subscribed) { +- case XPIDF_XML: +- case CPIM_PIDF_XML: +- add_header(&req, "Event", subscriptiontype->event); +- state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); +- add_header(&req, "Content-Type", subscriptiontype->mediatype); +- p->dialogver++; +- break; +- case PIDF_XML: /* Eyebeam supports this format */ +- add_header(&req, "Event", subscriptiontype->event); +- state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); +- add_header(&req, "Content-Type", subscriptiontype->mediatype); +- p->dialogver++; +- break; +- case DIALOG_INFO_XML: /* SNOM subscribes in this format */ +- add_header(&req, "Event", subscriptiontype->event); +- state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); +- add_header(&req, "Content-Type", subscriptiontype->mediatype); +- p->dialogver++; +- break; +- case NONE: +- default: +- break; +- } ++ add_header(&req, "Event", subscriptiontype->event); ++ state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); ++ add_header(&req, "Content-Type", subscriptiontype->mediatype); ++ p->dialogver++; + + add_content(&req, ast_str_buffer(tmp)); + +@@ -15624,7 +16141,7 @@ + (0/0) notification. This can temporarily be disabled in + sip.conf with the "buggymwi" option */ + ast_str_append(&out, 0, "Voice-Message: %d/%d%s\r\n", +- newmsgs, oldmsgs, (ast_test_flag(&p->flags[1], SIP_PAGE2_BUGGY_MWI) ? "" : " (0/0)")); ++ newmsgs, oldmsgs, (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) ? "" : " (0/0)")); + + if (p->subscribed) { + if (p->expiry) { +@@ -15783,10 +16300,6 @@ + if (!ast_test_flag(&p->flags[0], SIP_SENDRPID)) { + return; + } +- if (!connected_id.number.valid +- || ast_strlen_zero(connected_id.number.str)) { +- return; +- } + + append_history(p, "ConnectedLine", "%s party is now %s <%s>", + ast_test_flag(&p->flags[0], SIP_OUTGOING) ? "Calling" : "Called", +@@ -15796,7 +16309,12 @@ + if (ast_channel_state(p->owner) == AST_STATE_UP || ast_test_flag(&p->flags[0], SIP_OUTGOING)) { + struct sip_request req; + +- if (!p->pendinginvite && (p->invitestate == INV_CONFIRMED || p->invitestate == INV_TERMINATED)) { ++ if (is_method_allowed(&p->allowed_methods, SIP_UPDATE) && !ast_strlen_zero(p->okcontacturi)) { ++ reqprep(&req, p, SIP_UPDATE, 0, 1); ++ add_rpid(&req, p); ++ add_header(&req, "X-Asterisk-rpid-update", "Yes"); ++ send_request(p, &req, XMIT_CRITICAL, p->ocseq); ++ } else if (!p->pendinginvite && (p->invitestate == INV_CONFIRMED || p->invitestate == INV_TERMINATED)) { + reqprep(&req, p, ast_test_flag(&p->flags[0], SIP_REINVITE_UPDATE) ? SIP_UPDATE : SIP_INVITE, 0, 1); + + add_header(&req, "Allow", ALLOWED_METHODS); +@@ -15808,11 +16326,6 @@ + p->lastinvite = p->ocseq; + ast_set_flag(&p->flags[0], SIP_OUTGOING); + send_request(p, &req, XMIT_CRITICAL, p->ocseq); +- } else if ((is_method_allowed(&p->allowed_methods, SIP_UPDATE)) && (!ast_strlen_zero(p->okcontacturi))) { +- reqprep(&req, p, SIP_UPDATE, 0, 1); +- add_rpid(&req, p); +- add_header(&req, "X-Asterisk-rpid-update", "Yes"); +- send_request(p, &req, XMIT_CRITICAL, p->ocseq); + } else { + /* We cannot send the update yet, so we have to wait until we can */ + ast_set_flag(&p->flags[0], SIP_NEEDREINVITE); +@@ -15841,6 +16354,162 @@ + } + } + ++/*! \brief Notify peer that the do not disturb status has changed */ ++static int sip_send_donotdisturb(struct sip_peer *peer) ++{ ++ if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { ++ return 0; ++ } ++ ++ if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ struct sip_pvt *pvt; ++ struct ast_str *content = ast_str_alloca(8192); ++ ++ if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return -1; ++ } ++ ++ set_socket_transport(&pvt->socket, 0); ++ if (create_addr_from_peer(pvt, peer)) { ++ dialog_unlink_all(pvt); ++ dialog_unref(pvt, "create_addr_from_peer failed in sip_send_donotdisturb. Unref dialog"); ++ return -1; ++ } ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", peer->donotdisturb ? "enable" : "disable"); ++ ast_str_append(&content, 0, "\n", ast_test_flag(&pvt->flags[2], SIP_PAGE3_DND_BUSY) ? "callreject" : "ringeroff"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ transmit_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ } else if (peer->fepvt) { ++ struct sip_request req; ++ struct sip_pvt *pvt = peer->fepvt; ++ char tmp[512]; ++ ++ reqprep(&req, pvt, SIP_NOTIFY, 0, 1); ++ ++ add_header(&req, "Event", "as-feature-event"); ++ if (pvt->expiry) { ++ add_header(&req, "Subscription-State", "active"); ++ } else { ++ add_header(&req, "Subscription-State", "terminated;reason=timeout"); ++ } ++ add_header(&req, "Content-Type", "application/x-as-feature-event+xml"); ++ ++ add_content(&req, "\n"); ++ add_content(&req, "\n"); ++ snprintf(tmp, sizeof(tmp), "\n%s\n", peer->donotdisturb ? "true" : "false"); ++ add_content(&req, tmp); ++ add_content(&req, "\n"); ++ ++ send_request(pvt, &req, XMIT_RELIABLE, pvt->ocseq); ++ } ++ ++ return 0; ++} ++ ++/*! \brief Notify peer that the huntgroup login state has changed */ ++static int sip_send_huntgroup(struct sip_peer *peer) ++{ ++ if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { ++ return 0; ++ } ++ ++ if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ struct sip_pvt *pvt; ++ struct ast_str *content = ast_str_alloca(8192); ++ ++ if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return -1; ++ } ++ ++ set_socket_transport(&pvt->socket, 0); ++ if (create_addr_from_peer(pvt, peer)) { ++ dialog_unlink_all(pvt); ++ dialog_unref(pvt, "create_addr_from_peer failed in sip_send_huntgroup. Unref dialog"); ++ return -1; ++ } ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", peer->huntgroup ? "on" : "off"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ transmit_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ } ++ ++ return 0; ++} ++ ++/*! \brief Notify peer that the call forwarding extension has changed */ ++static int sip_send_callforward(struct sip_peer *peer) ++{ ++ if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { ++ return 0; ++ } ++ ++ if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ struct sip_pvt *pvt; ++ struct ast_str *content = ast_str_alloca(8192); ++ ++ if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return -1; ++ } ++ ++ set_socket_transport(&pvt->socket, 0); ++ if (create_addr_from_peer(pvt, peer)) { ++ dialog_unlink_all(pvt); ++ dialog_unref(pvt, "create_addr_from_peer failed in sip_send_callforward. Unref dialog"); ++ return -1; ++ } ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%d\n", peer->cisco_lineindex); ++ ast_str_append(&content, 0, "%s\n", peer->callforward); ++ ast_str_append(&content, 0, "%s\n", !ast_strlen_zero(peer->vmexten) && !strcmp(peer->callforward, peer->vmexten) ? "on" : "off"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ transmit_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ } else if (peer->fepvt) { ++ struct sip_request req; ++ struct sip_pvt *pvt = peer->fepvt; ++ char tmp[512]; ++ ++ reqprep(&req, pvt, SIP_NOTIFY, 0, 1); ++ ++ add_header(&req, "Event", "as-feature-event"); ++ if (pvt->expiry) { ++ add_header(&req, "Subscription-State", "active"); ++ } else { ++ add_header(&req, "Subscription-State", "terminated;reason=timeout"); ++ } ++ add_header(&req, "Content-Type", "application/x-as-feature-event+xml"); ++ ++ add_content(&req, "\n"); ++ add_content(&req, "\n"); ++ add_content(&req, "\nforwardImmediate\n"); ++ snprintf(tmp, sizeof(tmp), "%s\n%s\n", !ast_strlen_zero(peer->callforward) ? "true" : "false", peer->callforward); ++ add_content(&req, tmp); ++ add_content(&req, "\n"); ++ ++ send_request(pvt, &req, XMIT_RELIABLE, pvt->ocseq); ++ } ++ ++ return 0; ++} ++ + static const struct _map_x_s regstatestrings[] = { + { REG_STATE_FAILED, "Failed" }, + { REG_STATE_UNREGISTERED, "Unregistered"}, +@@ -16254,8 +16923,8 @@ + ast_string_field_set(p, username, r->username); + } + /* Save extension in packet */ +- if (!ast_strlen_zero(r->callback)) { +- ast_string_field_set(p, exten, r->callback); ++ if (!ast_strlen_zero(r->exten)) { ++ ast_string_field_set(p, exten, r->exten); + } + + /* Set transport so the correct contact is built */ +@@ -16381,6 +17050,9 @@ + { + sip_refer_destroy(p); + p->refer = ast_calloc_with_stringfields(1, struct sip_refer, 512); ++ if (p->refer) { ++ p->refer->content = ast_str_create(128); ++ } + return p->refer ? 1 : 0; + } + +@@ -16389,6 +17061,7 @@ + { + if (p->refer) { + ast_string_field_free_memory(p->refer); ++ ast_free(p->refer->content); + ast_free(p->refer); + p->refer = NULL; + } +@@ -16468,6 +17141,28 @@ + */ + } + ++/*! \brief Send an out-of-dialog SIP REFER message with content */ ++static int transmit_refer_with_content(struct sip_pvt *pvt, const char *content_type, const char *content) ++{ ++ /* Refer is outgoing call */ ++ ast_set_flag(&pvt->flags[0], SIP_OUTGOING); ++ sip_refer_alloc(pvt); ++ pvt->refer->status = REFER_SENT; ++ ++ ast_string_field_set(pvt->refer, require, "norefersub"); ++ ast_string_field_set(pvt->refer, referred_by, pvt->our_contact); ++ ast_string_field_build(pvt->refer, content_id, "%08lx", ast_random()); ++ ast_string_field_build(pvt->refer, refer_to, "cid:%s", pvt->refer->content_id); ++ ast_string_field_set(pvt->refer, content_type, content_type); ++ ++ ast_str_set(&pvt->refer->content, 0, "%s", content); ++ ++ sip_scheddestroy(pvt, SIP_TRANS_TIMEOUT); ++ sip_alreadygone(pvt); ++ ++ return transmit_invite(pvt, SIP_REFER, 0, 2, NULL); ++} ++ + /*! \brief Send SIP INFO advice of charge message */ + static int transmit_info_with_aoc(struct sip_pvt *p, struct ast_aoc_decoded *decoded) + { +@@ -16550,6 +17245,10 @@ + + if (sipmethod == SIP_ACK) { + p->invitestate = INV_CONFIRMED; ++ ++ if (ast_test_flag(&p->flags[2], SIP_PAGE3_SDP_ACK)) { ++ add_sdp(&resp, p, FALSE, TRUE, FALSE); ++ } + } + + return send_request(p, &resp, reliable, seqno ? seqno : p->ocseq); +@@ -16656,6 +17355,7 @@ + static int expire_register(const void *data) + { + struct sip_peer *peer = (struct sip_peer *)data; ++ struct sip_subscription *subscription; + + if (!peer) { /* Hmmm. We have no peer. Weird. */ + return 0; +@@ -16663,6 +17363,7 @@ + + peer->expire = -1; + peer->portinuri = 0; ++ ast_string_field_set(peer, regcallid, ""); + + destroy_association(peer); /* remove registration data from storage */ + set_socket_transport(&peer->socket, peer->default_outbound_transport); +@@ -16675,6 +17376,14 @@ + peer->socket.ws_session = NULL; + } + ++ AST_LIST_TRAVERSE(&peer->subscriptions, subscription, entry) { ++ if (subscription->pvt) { ++ dialog_unlink_all(subscription->pvt); ++ dialog_unref(subscription->pvt, "destroying subscription"); ++ subscription->pvt = NULL; ++ } ++ } ++ + if (peer->endpoint) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + ast_endpoint_set_state(peer->endpoint, AST_ENDPOINT_OFFLINE); +@@ -16707,6 +17416,7 @@ + /* Only clear the addr after we check for destruction. The addr must remain + * in order to unlink from the peers_by_ip container correctly */ + memset(&peer->addr, 0, sizeof(peer->addr)); ++ expire_peer_aliases(peer); + + sip_unref_peer(peer, "removing peer ref for expire_register"); + +@@ -16968,7 +17678,7 @@ + } + + /*! \brief Parse contact header and save registration (peer registration) */ +-static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, struct sip_peer *peer, struct sip_request *req) ++static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, struct sip_peer *peer, struct sip_request *req, int *addrchanged) + { + char contact[SIPBUFSIZE]; + char data[SIPBUFSIZE]; +@@ -17045,6 +17755,7 @@ + } + return PARSE_REGISTER_QUERY; + } else if (!strcasecmp(curi, "*") || !expire) { /* Unregister this peer */ ++ pvt->expiry = 0; + /* This means remove all registrations and return OK */ + AST_SCHED_DEL_UNREF(sched, peer->expire, + sip_unref_peer(peer, "remove register expire ref")); +@@ -17182,15 +17893,57 @@ + } + + /* Is this a new IP address for us? */ +- if (ast_sockaddr_cmp(&peer->addr, &oldsin)) { ++ if ((*addrchanged = ast_sockaddr_cmp(&peer->addr, &oldsin))) { + ast_verb(3, "Registered SIP '%s' at %s\n", peer->name, + ast_sockaddr_stringify(&peer->addr)); ++ ++ /* Clear off-hook counter in case of the on-hook notification not being received */ ++ peer->offhook = 0; + } + sip_pvt_unlock(pvt); + sip_poke_peer(peer, 0); + sip_pvt_lock(pvt); + register_peer_exten(peer, 1); + ++ /* Save REGISTER dialog Call-ID */ ++ ast_string_field_set(peer, regcallid, pvt->callid); ++ ++ if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ char reason[SIPBUFSIZE]; ++ ++ ast_copy_string(reason, sip_get_header(req, "Reason"), sizeof(reason)); ++ ++ if (!strncmp(reason, "SIP;cause=200;text=", 19)) { ++ char *devicename, *inactiveload, *activeload, *ext; ++ ++ if ((devicename = strstr(reason, " Name="))) { ++ devicename = ast_strdupa(devicename + 6); ++ devicename = strsep(&devicename, " "); ++ } ++ ++ if ((activeload = strstr(reason, " ActiveLoad=")) || ++ (activeload = strstr(reason, " Load="))) { ++ activeload = ast_strdupa(activeload + (!strncmp(activeload, " ActiveLoad=", 12) ? 12 : 6)); ++ if ((ext = strstr(activeload, ".loads"))) { ++ *ext = '\0'; ++ } ++ activeload = strsep(&activeload, " "); ++ } ++ ++ if ((inactiveload = strstr(reason, " InactiveLoad="))) { ++ inactiveload = ast_strdupa(inactiveload + 14); ++ if ((ext = strstr(inactiveload, ".loads"))) { ++ *ext = '\0'; ++ } ++ inactiveload = strsep(&inactiveload, " "); ++ } ++ ++ ast_string_field_set(peer, cisco_devicename, devicename); ++ ast_string_field_set(peer, cisco_activeload, activeload); ++ ast_string_field_set(peer, cisco_inactiveload, inactiveload); ++ } ++ } ++ + /* Save User agent */ + useragent = sip_get_header(req, "User-Agent"); + if (strcasecmp(useragent, peer->useragent)) { +@@ -17377,6 +18130,8 @@ + /* Always OK if no secret */ + if (ast_strlen_zero(secret) && ast_strlen_zero(md5secret)) { + return AUTH_SUCCESSFUL; ++ } else if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && (sipmethod == SIP_REFER || sipmethod == SIP_PUBLISH)) { ++ return AUTH_SUCCESSFUL; /* Buggy Cisco USECALLMANAGER phones can't auth REFER or PUBLISH correctly */ + } + + /* Always auth with WWW-auth since we're NOT a proxy */ +@@ -17550,7 +18305,7 @@ + } + + if (ast_mwi_state_type() == stasis_message_type(msg)) { +- sip_send_mwi_to_peer(peer, 0); ++ sip_send_mwi(peer, 0); + } + } + +@@ -17607,6 +18362,219 @@ + } + } + ++struct pickup_notify_args { ++ ast_group_t callgroup; ++ struct ast_namedgroups *named_callgroups; ++ time_t now; ++}; ++ ++static int pickup_notify_cb(void *obj, void *arg, int flags) ++{ ++ struct sip_peer *peer = obj; ++ struct pickup_notify_args *args = arg; ++ ++ ao2_lock(peer); ++ ++ if (!ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) || ++ !ast_test_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM | SIP_PAGE3_CISCO_PICKUPNOTIFY_TO | SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP)) { ++ ao2_unlock(peer); ++ return 0; ++ } ++ ++ if (ast_sockaddr_isnull(&peer->addr) || peer->ringing || peer->inuse || peer->donotdisturb) { ++ ao2_unlock(peer); ++ return 0; ++ } ++ ++ if ((peer->pickupgroup & args->callgroup) || ast_namedgroups_intersect(peer->named_pickupgroups, args->named_callgroups)) { ++ if (args->now - peer->cisco_pickupnotify_sent > peer->cisco_pickupnotify_timer) { ++ peer->cisco_pickupnotify_sent = args->now; ++ ao2_unlock(peer); ++ ++ return CMP_MATCH; ++ } ++ } ++ ++ ao2_unlock(peer); ++ ++ return 0; ++} ++ ++static void *pickup_notify_thread(void *obj) ++{ ++ char *device = obj; ++ char match[AST_CHANNEL_NAME]; ++ struct ast_channel_iterator *chaniter; ++ struct ast_channel *pickupchan = NULL, *chan; ++ struct timeval creationtime = { 0, }; ++ struct pickup_notify_args args; ++ char *cid_num, *lid_num; ++ struct ao2_iterator *peeriter; ++ struct sip_peer *peer; ++ struct sip_pvt *pvt; ++ struct ast_str *content = ast_str_alloca(8192); ++ ++ snprintf(match, sizeof(match), "%s-", device); ++ chaniter = ast_channel_iterator_by_name_new(match, strlen(match)); ++ ast_free(device); ++ ++ while ((chan = ast_channel_iterator_next(chaniter))) { ++ ast_channel_lock(chan); ++ ++ /* Pick the youngest ringing channel */ ++ if (ast_channel_state(chan) == AST_STATE_RINGING && ast_tvcmp(ast_channel_creationtime(chan), creationtime) > 0) { ++ if (pickupchan) { ++ ast_channel_unref(pickupchan); ++ } ++ pickupchan = ast_channel_ref(chan); ++ creationtime = ast_channel_creationtime(chan); ++ } ++ ++ ast_channel_unlock(chan); ++ ast_channel_unref(chan); ++ } ++ ++ ast_channel_iterator_destroy(chaniter); ++ ++ if (!pickupchan) { ++ return NULL; ++ } ++ ++ ast_channel_lock(pickupchan); ++ ++ args.callgroup = ast_channel_callgroup(pickupchan); ++ args.named_callgroups = ast_ref_namedgroups(ast_channel_named_callgroups(pickupchan)); ++ ++ cid_num = ast_strdupa(S_COR(ast_channel_caller(pickupchan)->id.number.valid, ast_channel_caller(pickupchan)->id.number.str, "")); ++ lid_num = ast_strdupa(S_COR(ast_channel_connected(pickupchan)->id.number.valid, ast_channel_connected(pickupchan)->id.number.str, CALLERID_UNKNOWN)); ++ ++ ast_channel_unlock(pickupchan); ++ ast_channel_unref(pickupchan); ++ ++ if (!args.callgroup && !args.named_callgroups) { ++ return NULL; ++ } ++ ++ args.now = time(NULL); ++ /* We use ao2_callback here so that we don't hold the lock on the peers container while sending the notify dialogs */ ++ ao2_lock(peers); ++ ++ if (!(peeriter = ao2_callback(peers, OBJ_MULTIPLE, pickup_notify_cb, &args))) { ++ ast_log(LOG_ERROR, "Unable to create iterator for peers container in pickup_notify_thread\n"); ++ ao2_unlock(peers); ++ return NULL; ++ } ++ ++ ao2_unlock(peers); ++ ast_unref_namedgroups(args.named_callgroups); ++ ++ while ((peer = ao2_iterator_next(peeriter))) { ++ if ((!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0)))) { ++ sip_unref_peer(peer, "remove iterator ref"); ++ continue; ++ } ++ ++ set_socket_transport(&pvt->socket, 0); ++ if (create_addr_from_peer(pvt, peer)) { ++ dialog_unlink_all(pvt); ++ dialog_unref(pvt, "create_addr_from_peer failed in pickup_notify_thread. Unref dialog"); ++ sip_unref_peer(peer, "remove iterator ref"); ++ continue; ++ } ++ ++ ast_str_reset(content); ++ ++ if (ast_test_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM | SIP_PAGE3_CISCO_PICKUPNOTIFY_TO)) { ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "notify_display\n"); ++ ast_str_append(&content, 0, ""); ++ if (ast_test_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM)) { ++ ast_str_append(&content, 0, "From %s", lid_num); ++ } ++ if (ast_test_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_TO)) { ++ ast_str_append(&content, 0, "%s %s", ++ ast_test_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM) ? " to" : "To", cid_num); ++ } ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%d\n", peer->cisco_pickupnotify_timer); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "1\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ } ++ ++ if (ast_test_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP)) { ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "DtZipZip\n"); ++ ast_str_append(&content, 0, "all\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ } ++ ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ ++ sip_unref_peer(peer, "remove iterator ref"); ++ } ++ ++ ao2_iterator_destroy(peeriter); ++ ++ return NULL; ++} ++ ++static void pickup_notify_stasis_subscribe(void) ++{ ++ if (!pickup_notify_sub) { ++ pickup_notify_sub = stasis_subscribe(ast_device_state_topic_all(), pickup_notify_stasis_cb, NULL); ++ } ++} ++ ++static void pickup_notify_stasis_unsubscribe(void) ++{ ++ if (pickup_notify_sub) { ++ pickup_notify_sub = stasis_unsubscribe(pickup_notify_sub); ++ } ++} ++ ++static void pickup_notify_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) ++{ ++ struct ast_device_state_message *device_state; ++ char *device; ++ pthread_t threadid; ++ ++ if (stasis_message_type(message) != ast_device_state_message_type()) { ++ return; ++ } ++ ++ device_state = stasis_message_data(message); ++ ++ if (device_state->state != AST_DEVICE_RINGING) { ++ return; ++ } ++ ++ if ((device = ast_strdup(device_state->device)) == NULL) { ++ return; ++ } ++ ++ if (ast_pthread_create_detached_background(&threadid, NULL, pickup_notify_thread, device)) { ++ ast_free(device); ++ } ++} ++ + static void cb_extensionstate_destroy(int id, void *data) + { + struct sip_pvt *p = data; +@@ -17634,8 +18602,8 @@ + /* we must skip the next two checks for a queued state change or resubscribe */ + } else if ((p->laststate == data->state && (~data->state & AST_EXTENSION_RINGING)) && + (p->last_presence_state == data->presence_state && +- !strcmp(p->last_presence_subtype, data->presence_subtype) && +- !strcmp(p->last_presence_message, data->presence_message))) { ++ !strcmp(p->last_presence_subtype, S_OR(data->presence_subtype, "")) && ++ !strcmp(p->last_presence_message, S_OR(data->presence_message, "")))) { + /* don't notify unchanged state or unchanged early-state causing parties again */ + sip_pvt_unlock(p); + return 0; +@@ -17687,7 +18655,7 @@ + } + + if (!force) { +- ast_verb(2, "Extension Changed %s[%s] new state %s for Notify User %s %s\n", exten, context, ast_extension_state2str(data->state), p->username, ++ ast_debug(1, "Extension Changed %s[%s] new state %s for Notify User %s %s\n", exten, context, ast_extension_state2str(data->state), p->username, + ast_test_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE) ? "(queued)" : ""); + } + +@@ -17710,12 +18678,126 @@ + .presence_message = info->presence_message, + }; + +- if ((info->reason == AST_HINT_UPDATE_PRESENCE) && !(allow_notify_user_presence(p))) { +- /* ignore a presence triggered update if we know the useragent doesn't care */ +- return 0; ++ return extensionstate_update(context, exten, ¬ify_data, p, FALSE); ++} ++ ++/*! ++ * \brief Send initial subscription state updates to peer ++ */ ++static void extensionstate_subscriptions(struct sip_peer *peer) ++{ ++ struct sip_subscription *subscription; ++ ++ if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { ++ return; + } + +- return extensionstate_update(context, exten, ¬ify_data, p, FALSE); ++ AST_LIST_TRAVERSE(&peer->subscriptions, subscription, entry) { ++ struct sip_request req; ++ struct ao2_container *device_state_info = NULL; ++ struct state_notify_data notify_data = { 0, }; ++ char *subtype = NULL, *message = NULL; ++ ++ if (subscription->pvt) { ++ /* Peer hasn't changed, keep original pvt */ ++ if (!ast_sockaddr_cmp(&peer->addr, &subscription->pvt->sa)) { ++ continue; ++ } ++ ++ dialog_unlink_all(subscription->pvt); ++ dialog_unref(subscription->pvt, "drop pvt"); ++ subscription->pvt = NULL; ++ } ++ ++ if (!subscription->pvt) { ++ if (!(subscription->pvt = sip_alloc(NULL, NULL, 0, SIP_NOTIFY, NULL, 0))) { ++ return; ++ } ++ } ++ ++ /* Don't use create_addr_from_peer here as it may fail due to the peer not having responded to an OPTIONS request yet */ ++ if (!ast_strlen_zero(peer->tohost)) { ++ ast_string_field_set(subscription->pvt, tohost, peer->tohost); ++ } else { ++ ast_string_field_set(subscription->pvt, tohost, ast_sockaddr_stringify_host_remote(&peer->addr)); ++ } ++ ++ subscription->pvt->portinuri = peer->portinuri; ++ subscription->pvt->fromdomainport = peer->fromdomainport; ++ ++ ast_string_field_set(subscription->pvt, fullcontact, peer->fullcontact); ++ ast_string_field_set(subscription->pvt, username, peer->username); ++ ast_string_field_set(subscription->pvt, fromuser, subscription->exten); ++ ast_string_field_set(subscription->pvt, fromname, ""); ++ ++ ast_string_field_set(subscription->pvt, context, subscription->context); ++ ast_string_field_set(subscription->pvt, exten, subscription->exten); ++ ast_string_field_build(subscription->pvt, subscribeuri, "%s@%s", subscription->exten, subscription->context); ++ ++ ast_copy_flags(&subscription->pvt->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY); ++ ast_copy_flags(&subscription->pvt->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY); ++ ast_copy_flags(&subscription->pvt->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY); ++ ++ /* Notify is outgoing call */ ++ ast_set_flag(&subscription->pvt->flags[0], SIP_OUTGOING); ++ ast_set_flag(&subscription->pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); ++ ast_set_flag(&subscription->pvt->flags[2], SIP_PAGE3_SUBSCRIPTIONSTATE_ACTIVE); ++ ++ subscription->pvt->subscribed = PIDF_XML; /* Needs to be configurable */ ++ subscription->pvt->expiry = 0; ++ ++ subscription->pvt->sa = peer->addr; ++ subscription->pvt->recv = peer->addr; ++ copy_socket_data(&subscription->pvt->socket, &peer->socket); ++ ++ /* Recalculate our side, and recalculate Call ID */ ++ ast_sip_ouraddrfor(&subscription->pvt->sa, &subscription->pvt->ourip, subscription->pvt); ++ change_callid_pvt(subscription->pvt, NULL); ++ ++ initreqprep(&req, subscription->pvt, SIP_NOTIFY, NULL); ++ initialize_initreq(subscription->pvt, &req); ++ ++ /* Because we only use this req to initialize the pvt's initreq we have to manually deallocate it */ ++ deinit_req(&req); ++ ++ subscription->pvt->stateid = ast_extension_state_add_extended(subscription->pvt->context, subscription->pvt->exten, cb_extensionstate, subscription->pvt); ++ ++ if (subscription->pvt->stateid == -1) { ++ dialog_unlink_all(subscription->pvt); ++ dialog_unref(subscription->pvt, "drop pvt"); ++ subscription->pvt = NULL; ++ ++ continue; ++ } ++ ++ ast_debug(1, "Adding subscription for %s@%s (%s)\n", subscription->exten, subscription->context, subscription->pvt->callid); ++ ++ notify_data.state = ast_extension_state_extended(NULL, subscription->context, subscription->exten, &device_state_info); ++ if (notify_data.state < 0) { ++ ao2_cleanup(device_state_info); ++ continue; ++ } ++ ++ notify_data.presence_state = ast_hint_presence_state(NULL, subscription->context, subscription->exten, &subtype, &message); ++ notify_data.presence_subtype = subtype; ++ notify_data.presence_message = message; ++ notify_data.device_state_info = device_state_info; ++ ++ if (notify_data.state & AST_EXTENSION_RINGING) { ++ struct ast_channel *ringing = find_ringing_channel(notify_data.device_state_info, NULL); ++ ++ if (ringing) { ++ subscription->pvt->last_ringing_channel_time = ast_channel_creationtime(ringing); ++ ao2_ref(ringing, -1); ++ } ++ } ++ ++ extensionstate_update(subscription->context, subscription->exten, ¬ify_data, subscription->pvt, TRUE); ++ ++ ao2_cleanup(device_state_info); ++ ast_free(subtype); ++ ast_free(message); ++ } + } + + /*! \brief Send a fake 401 Unauthorized response when the administrator +@@ -17876,7 +18958,7 @@ + char tmp[256]; + char *c, *name, *unused_password, *domain; + char *uri2 = ast_strdupa(uri); +- int send_mwi = 0; ++ int addrchanged, registered = 0; + + terminate_uri(uri2); + +@@ -17946,6 +19028,16 @@ + } + peer = sip_find_peer(name, NULL, TRUE, FINDPEERS, FALSE, 0); + ++ /* Cisco USECALLMANAGER failover */ ++ if (peer && ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ const char *contact = sip_get_header(req, "Contact"); ++ ++ if (strcasestr(contact, ";expires=0;cisco-keep-alive")) { ++ transmit_response_with_date(p, "200 OK", req); ++ return 0; ++ } ++ } ++ + /* If we don't want username disclosure, use the bogus_peer when a user + * is not found. */ + if (!peer && sip_cfg.alwaysauthreject && sip_cfg.autocreatepeer == AUTOPEERS_DISABLED) { +@@ -17985,7 +19077,7 @@ + + /* We have a successful registration attempt with proper authentication, + now, update the peer */ +- switch (parse_register_contact(p, peer, req)) { ++ switch (parse_register_contact(p, peer, req, &addrchanged)) { + case PARSE_REGISTER_DENIED: + ast_log(LOG_WARNING, "Registration denied because of contact ACL\n"); + transmit_response_with_date(p, "603 Denied", req); +@@ -18000,7 +19092,6 @@ + ast_string_field_set(p, fullcontact, peer->fullcontact); + transmit_response_with_date(p, "200 OK", req); + res = 0; +- send_mwi = 1; + break; + case PARSE_REGISTER_UPDATE: + ast_string_field_set(p, fullcontact, peer->fullcontact); +@@ -18008,9 +19099,14 @@ + if (p->expiry != 0) { + update_peer(peer, p->expiry); + } ++ ast_set2_flag(&p->flags[1], ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER), SIP_PAGE2_CISCO_USECALLMANAGER); + /* Say OK and ask subsystem to retransmit msg counter */ +- transmit_response_with_date(p, "200 OK", req); +- send_mwi = 1; ++ if (p->expiry != 0 && ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && addrchanged) { ++ transmit_response_with_optionsind(p, req); ++ } else { ++ transmit_response_with_date(p, "200 OK", req); ++ } ++ registered = 1; + res = 0; + break; + } +@@ -18034,7 +19130,7 @@ + } + ao2_lock(peer); + sip_cancel_destroy(p); +- switch (parse_register_contact(p, peer, req)) { ++ switch (parse_register_contact(p, peer, req, &addrchanged)) { + case PARSE_REGISTER_DENIED: + ast_log(LOG_WARNING, "Registration denied because of contact ACL\n"); + transmit_response_with_date(p, "403 Forbidden", req); +@@ -18048,13 +19144,17 @@ + case PARSE_REGISTER_QUERY: + ast_string_field_set(p, fullcontact, peer->fullcontact); + transmit_response_with_date(p, "200 OK", req); +- send_mwi = 1; + res = 0; + break; + case PARSE_REGISTER_UPDATE: + ast_string_field_set(p, fullcontact, peer->fullcontact); ++ ast_set2_flag(&p->flags[1], ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER), SIP_PAGE2_CISCO_USECALLMANAGER); + /* Say OK and ask subsystem to retransmit msg counter */ +- transmit_response_with_date(p, "200 OK", req); ++ if (p->expiry != 0 && ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && addrchanged) { ++ transmit_response_with_optionsind(p, req); ++ } else { ++ transmit_response_with_date(p, "200 OK", req); ++ } + if (peer->endpoint) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + ast_endpoint_set_state(peer->endpoint, AST_ENDPOINT_ONLINE); +@@ -18063,7 +19163,7 @@ + "address", ast_sockaddr_stringify(addr)); + ast_endpoint_blob_publish(peer->endpoint, ast_endpoint_state_type(), blob); + } +- send_mwi = 1; ++ registered = 1; + res = 0; + break; + } +@@ -18071,10 +19171,19 @@ + } + } + if (!res) { +- if (send_mwi) { +- sip_pvt_unlock(p); +- sip_send_mwi_to_peer(peer, 0); +- sip_pvt_lock(p); ++ if (p->expiry != 0 && registered) { ++ if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ /* We only need to do an update if the peer addr has changed */ ++ if (addrchanged) { ++ register_peer_aliases(peer); ++ sip_send_bulkupdate(peer); ++ extensionstate_subscriptions(peer); ++ } ++ } else { ++ sip_pvt_unlock(p); ++ sip_send_mwi(peer, 0); ++ sip_pvt_lock(p); ++ } + } else { + update_peer_lastmsgssent(peer, -1, 0); + } +@@ -18193,82 +19302,6 @@ + } + } + +-/*! \brief Parse the parts of the P-Asserted-Identity header +- * on an incoming packet. Returns 1 if a valid header is found +- * and it is different from the current caller id. +- */ +-static int get_pai(struct sip_pvt *p, struct sip_request *req) +-{ +- char pai[256]; +- char privacy[64]; +- char *cid_num = NULL; +- char *cid_name = NULL; +- char emptyname[1] = ""; +- int callingpres = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED; +- char *uri = NULL; +- int is_anonymous = 0, do_update = 1, no_name = 0; +- +- ast_copy_string(pai, sip_get_header(req, "P-Asserted-Identity"), sizeof(pai)); +- +- if (ast_strlen_zero(pai)) { +- return 0; +- } +- +- /* use the reqresp_parser function get_name_and_number*/ +- if (get_name_and_number(pai, &cid_name, &cid_num)) { +- return 0; +- } +- +- if (global_shrinkcallerid && ast_is_shrinkable_phonenumber(cid_num)) { +- ast_shrink_phone_number(cid_num); +- } +- +- uri = get_in_brackets(pai); +- if (!strncasecmp(uri, "sip:anonymous@anonymous.invalid", 31)) { +- callingpres = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED; +- /*XXX Assume no change in cid_num. Perhaps it should be +- * blanked? +- */ +- ast_free(cid_num); +- is_anonymous = 1; +- cid_num = (char *)p->cid_num; +- } +- +- ast_copy_string(privacy, sip_get_header(req, "Privacy"), sizeof(privacy)); +- if (!ast_strlen_zero(privacy) && strcasecmp(privacy, "none")) { +- callingpres = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED; +- } +- if (!cid_name) { +- no_name = 1; +- cid_name = (char *)emptyname; +- } +- /* Only return true if the supplied caller id is different */ +- if (!strcasecmp(p->cid_num, cid_num) && !strcasecmp(p->cid_name, cid_name) && p->callingpres == callingpres) { +- do_update = 0; +- } else { +- +- ast_string_field_set(p, cid_num, cid_num); +- ast_string_field_set(p, cid_name, cid_name); +- p->callingpres = callingpres; +- +- if (p->owner) { +- ast_set_callerid(p->owner, cid_num, cid_name, NULL); +- ast_channel_caller(p->owner)->id.name.presentation = callingpres; +- ast_channel_caller(p->owner)->id.number.presentation = callingpres; +- } +- } +- +- /* get_name_and_number allocates memory for cid_num and cid_name so we have to free it */ +- if (!is_anonymous) { +- ast_free(cid_num); +- } +- if (!no_name) { +- ast_free(cid_name); +- } +- +- return do_update; +-} +- + /*! \brief Get name, number and presentation from remote party id header, + * returns true if a valid header was found and it was different from the + * current caller id. +@@ -18283,6 +19316,7 @@ + char *privacy = ""; + char *screen = ""; + char *start, *end; ++ int pai; + + if (!ast_test_flag(&p->flags[0], SIP_TRUSTRPID)) + return 0; +@@ -18291,7 +19325,13 @@ + req = &p->initreq; + ast_copy_string(tmp, sip_get_header(req, "Remote-Party-ID"), sizeof(tmp)); + if (ast_strlen_zero(tmp)) { +- return get_pai(p, req); ++ ast_copy_string(tmp, sip_get_header(req, "P-Asserted-Identity"), sizeof(tmp)); ++ if (ast_strlen_zero(tmp)) { ++ return 0; ++ } ++ pai = 1; ++ } else { ++ pai = 0; + } + + /* +@@ -18343,7 +19383,13 @@ + if (!end) + return 0; + *end++ = '\0'; +- if (*end) { ++ if (pai) { ++ if (!strcasecmp(sip_get_header(req, "Privacy"), "id")) { ++ callingpres = AST_PRES_PROHIB_USER_NUMBER_PASSED_SCREEN; ++ } else if (!strcasecmp(cid_num, "anonymous@anonymous.invalid")) { ++ callingpres = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED; ++ } ++ } else if (*end) { + start = end; + if (*start != ';') + return 0; +@@ -18683,6 +19729,14 @@ + return SIP_GET_DEST_EXTEN_NOT_FOUND; + } + ++/*! \brief Find a companion dialog */ ++struct sip_pvt *get_sip_pvt(const char *callid, const char *tag, const char *theirtag) ++{ ++ struct sip_pvt dialog = { .callid = callid, .tag = tag, .theirtag = theirtag }; ++ ++ return ao2_find(dialogs, &dialog, OBJ_POINTER); ++} ++ + /*! \brief Find a companion dialog based on Replaces information + * + * This information may come from a Refer-To header in a REFER or from +@@ -19265,10 +20319,10 @@ + + /* Then find devices based on IP */ + if (!peer) { +- char *uri_tmp, *callback = NULL, *dummy; ++ char *uri_tmp, *callbackexten = NULL, *dummy; + uri_tmp = ast_strdupa(uri2); +- parse_uri(uri_tmp, "sip:,sips:,tel:", &callback, &dummy, &dummy, &dummy); +- if (!ast_strlen_zero(callback) && (peer = sip_find_peer_by_ip_and_exten(&p->recv, callback, p->socket.type))) { ++ parse_uri(uri_tmp, "sip:,sips:,tel:", &callbackexten, &dummy, &dummy, &dummy); ++ if (!ast_strlen_zero(callbackexten) && (peer = sip_find_peer_by_ip_and_exten(&p->recv, callbackexten, p->socket.type))) { + ; /* found, fall through */ + } else { + peer = sip_find_peer(NULL, &p->recv, TRUE, FINDPEERS, FALSE, p->socket.type); +@@ -19336,6 +20390,7 @@ + + do_setnat(p); + ++ ast_string_field_set(p, authname, peer->name); + ast_string_field_set(p, peersecret, peer->secret); + ast_string_field_set(p, peermd5secret, peer->md5secret); + ast_string_field_set(p, subscribecontext, peer->subscribecontext); +@@ -19363,12 +20418,17 @@ + + p->allowtransfer = peer->allowtransfer; + ++ /* Cisco peers only auth using the credentials of the primary peer */ ++ if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && peer->cisco_lineindex > 1) { ++ ast_string_field_set(p, authname, peer->cisco_authname); ++ } ++ + if (ast_test_flag(&peer->flags[0], SIP_INSECURE_INVITE)) { + /* Pretend there is no required authentication */ + ast_string_field_set(p, peersecret, NULL); + ast_string_field_set(p, peermd5secret, NULL); + } +- if (!(res = check_auth(p, req, peer->name, p->peersecret, p->peermd5secret, sipmethod, uri2, reliable))) { ++ if (!(res = check_auth(p, req, p->authname, p->peersecret, p->peermd5secret, sipmethod, uri2, reliable))) { + + /* build_peer, called through sip_find_peer, is not able to check the + * sip_pvt->natdetected flag in order to determine if the peer is behind +@@ -19736,7 +20796,7 @@ + return; + } + +- if (!(buf = get_content(req))) { ++ if (!(buf = get_content(req, 0, req->lines))) { + ast_log(LOG_WARNING, "Unable to retrieve text from %s\n", p->callid); + transmit_response(p, "500 Internal Server Error", req); + if (!p->owner) { +@@ -21166,7 +22226,7 @@ + } + + /*! \brief list peer mailboxes to CLI */ +-static void peer_mailboxes_to_str(struct ast_str **mailbox_str, struct sip_peer *peer) ++static void get_peer_mailboxes(struct ast_str **mailbox_str, struct sip_peer *peer) + { + struct sip_mailbox *mailbox; + +@@ -21199,6 +22259,8 @@ + struct ast_variable *v; + int x = 0, load_realtime; + int realtimepeers; ++ struct sip_alias *alias; ++ struct sip_subscription *subscription; + + realtimepeers = ast_check_realtime("sippeers"); + +@@ -21279,7 +22341,7 @@ + print_named_groups(fd, peer->named_callgroups, 0); + ast_cli(fd, " Nam. Pickupgr: "); + print_named_groups(fd, peer->named_pickupgroups, 0); +- peer_mailboxes_to_str(&mailbox_str, peer); ++ get_peer_mailboxes(&mailbox_str, peer); + ast_cli(fd, " MOH Suggest : %s\n", peer->mohsuggest); + ast_cli(fd, " Mailbox : %s\n", ast_str_buffer(mailbox_str)); + ast_cli(fd, " VM Extension : %s\n", peer->vmexten); +@@ -21358,9 +22420,9 @@ + ast_cli(fd, " Qualify Freq : %d ms\n", peer->qualifyfreq); + ast_cli(fd, " Keepalive : %d ms\n", peer->keepalive * 1000); + if (peer->chanvars) { +- ast_cli(fd, " Variables :\n"); ++ ast_cli(fd, " Variables : "); + for (v = peer->chanvars ; v ; v = v->next) +- ast_cli(fd, " %s = %s\n", v->name, v->value); ++ ast_cli(fd, "%s%s = %s\n", v != peer->chanvars ? " " : "", v->name, v->value); + } + + ast_cli(fd, " Sess-Timers : %s\n", stmode2str(peer->stimer.st_mode_oper)); +@@ -21372,6 +22434,28 @@ + ast_cli(fd, " Use Reason : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_Q850_REASON))); + ast_cli(fd, " Encryption : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_USE_SRTP))); + ast_cli(fd, " RTCP Mux : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[2], SIP_PAGE3_RTCP_MUX))); ++ ast_cli(fd, " DND : %s\n", AST_CLI_YESNO(peer->donotdisturb)); ++ ast_cli(fd, " CallFwd Ext. : %s\n", peer->callforward); ++ ast_cli(fd, " Hunt Group : %s\n", AST_CLI_YESNO(peer->huntgroup)); ++ if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ ast_cli(fd, " Device Name : %s\n", peer->cisco_devicename); ++ ast_cli(fd, " Active Load : %s\n", peer->cisco_activeload); ++ ast_cli(fd, " Inactive Load: %s\n", peer->cisco_inactiveload); ++ ++ if (!AST_LIST_EMPTY(&peer->aliases)) { ++ ast_cli(fd, " BulkReg.Peers: "); ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ ast_cli(fd, "%s%s (Line %d)\n", alias != AST_LIST_FIRST(&peer->aliases) ? " " : "", alias->name, alias->lineindex); ++ } ++ } ++ ++ if (!AST_LIST_EMPTY(&peer->subscriptions)) { ++ ast_cli(fd, " Subscriptions: "); ++ AST_LIST_TRAVERSE(&peer->subscriptions, subscription, entry) { ++ ast_cli(fd, "%s%s@%s\n", subscription != AST_LIST_FIRST(&peer->subscriptions) ? " " : "", subscription->exten, subscription->context); ++ } ++ } ++ } + ast_cli(fd, "\n"); + peer = sip_unref_peer(peer, "sip_show_peer: sip_unref_peer: done with peer ptr"); + } else if (peer && type == 1) { /* manager listing */ +@@ -21408,7 +22492,7 @@ + astman_append(s, "%s\r\n", ast_print_namedgroups(&tmp_str, peer->named_pickupgroups)); + ast_str_reset(tmp_str); + astman_append(s, "MOHSuggest: %s\r\n", peer->mohsuggest); +- peer_mailboxes_to_str(&tmp_str, peer); ++ get_peer_mailboxes(&tmp_str, peer); + astman_append(s, "VoiceMailbox: %s\r\n", ast_str_buffer(tmp_str)); + astman_append(s, "TransferMode: %s\r\n", transfermode2str(peer->allowtransfer)); + astman_append(s, "LastMsgsSent: %d\r\n", peer->lastmsgssent); +@@ -21467,7 +22551,19 @@ + } + astman_append(s, "SIP-Use-Reason-Header: %s\r\n", (ast_test_flag(&peer->flags[1], SIP_PAGE2_Q850_REASON)) ? "Y" : "N"); + astman_append(s, "Description: %s\r\n", peer->description); +- ++ astman_append(s, "DoNotDisturb: %s\r\n", peer->donotdisturb ? "Y" : "N"); ++ astman_append(s, "CallForward: %s\r\n", peer->callforward); ++ astman_append(s, "HuntGroup: %s\r\n", peer->huntgroup ? "Y" : "N"); ++ astman_append(s, "CiscoDeviceName: %s\r\n", peer->cisco_devicename); ++ astman_append(s, "CiscoActiveLoad: %s\r\n", peer->cisco_activeload); ++ astman_append(s, "CiscoInactiveLoad: %s\r\n", peer->cisco_inactiveload); ++ astman_append(s, "CiscoLineIndex: %d\r\n", peer->cisco_lineindex); ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ astman_append(s, "Register: %s\r\n", alias->name); ++ } ++ AST_LIST_TRAVERSE(&peer->subscriptions, subscription, entry) { ++ astman_append(s, "Subscribe: %s@%s\r\n", subscription->exten, subscription->context); ++ } + peer = sip_unref_peer(peer, "sip_show_peer: sip_unref_peer: done with peer"); + + } else { +@@ -21582,9 +22678,9 @@ + + ast_cli(a->fd, " Auto-Framing: %s \n", AST_CLI_YESNO(user->autoframing)); + if (user->chanvars) { +- ast_cli(a->fd, " Variables :\n"); ++ ast_cli(a->fd, " Variables : "); + for (v = user->chanvars ; v ; v = v->next) +- ast_cli(a->fd, " %s = %s\n", v->name, v->value); ++ ast_cli(a->fd, "%s%s = %s\n", v != user->chanvars ? " " : "", v->name, v->value); + } + + ast_cli(a->fd, "\n"); +@@ -22192,7 +23288,7 @@ + if (cur->subscribed != NONE && arg->subscriptions) { + struct ast_str *mailbox_str = ast_str_alloca(512); + if (cur->subscribed == MWI_NOTIFICATION && cur->relatedpeer) +- peer_mailboxes_to_str(&mailbox_str, cur->relatedpeer); ++ get_peer_mailboxes(&mailbox_str, cur->relatedpeer); + ast_cli(arg->fd, FORMAT4, ast_sockaddr_stringify_addr(dst), + S_OR(cur->username, S_OR(cur->cid_num, "(None)")), + cur->callid, +@@ -22678,7 +23774,7 @@ + } + } else { + /* Type is application/dtmf, simply use what's in the message body */ +- buf = get_content(req); ++ buf = get_content(req, 0, req->lines); + } + + /* An empty message body requires us to send a 200 OK */ +@@ -22906,6 +24002,8 @@ + static char *sip_cli_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) + { + struct ast_variable *varlist; ++ struct ast_channel *chan; ++ struct ast_str *str; + int i; + + switch (cmd) { +@@ -22935,10 +24033,21 @@ + return CLI_FAILURE; + } + ++ if (!(chan = ast_dummy_channel_alloc())) { ++ ast_cli(a->fd, "Cannot allocate the channel for variables substitution\n"); ++ return CLI_FAILURE; ++ } ++ ++ if (!(str = ast_str_create(32))) { ++ ast_channel_release(chan); ++ ast_cli(a->fd, "Cannot allocate the string for variables substitution\n"); ++ return CLI_FAILURE; ++ } ++ + for (i = 3; i < a->argc; i++) { + struct sip_pvt *p; + char buf[512]; +- struct ast_variable *header, *var; ++ struct ast_variable *var, *header, *headers = NULL; + + if (!(p = sip_alloc(NULL, NULL, 0, SIP_NOTIFY, NULL, 0))) { + ast_log(LOG_WARNING, "Unable to build sip pvt data for notify (memory/socket error)\n"); +@@ -22957,36 +24066,220 @@ + /* Notify is outgoing call */ + ast_set_flag(&p->flags[0], SIP_OUTGOING); + sip_notify_alloc(p); +- p->notify->headers = header = ast_variable_new("Subscription-State", "terminated", ""); ++ ++ /* Recalculate our side, and recalculate Call ID */ ++ ast_sip_ouraddrfor(&p->sa, &p->ourip, p); ++ change_callid_pvt(p, NULL); ++ ++ /* Set the name of the peer being sent the notification so it can be used in ${} functions */ ++ pbx_builtin_setvar_helper(chan, "PEERNAME", p->peername); + + for (var = varlist; var; var = var->next) { + ast_copy_string(buf, var->value, sizeof(buf)); + ast_unescape_semicolon(buf); ++ ast_str_substitute_variables(&str, 0, chan, buf); + + if (!strcasecmp(var->name, "Content")) { + if (ast_str_strlen(p->notify->content)) + ast_str_append(&p->notify->content, 0, "\r\n"); +- ast_str_append(&p->notify->content, 0, "%s", buf); ++ ast_str_append(&p->notify->content, 0, "%s", ast_str_buffer(str)); + } else if (!strcasecmp(var->name, "Content-Length")) { + ast_log(LOG_WARNING, "it is not necessary to specify Content-Length in sip_notify.conf, ignoring\n"); + } else { +- header->next = ast_variable_new(var->name, buf, ""); +- header = header->next; ++ header = ast_variable_new(var->name, ast_str_buffer(str), ""); ++ if (headers) { ++ headers->next = header; ++ } else { ++ p->notify->headers = header; ++ } ++ headers = header; + } + } + +- /* Now that we have the peer's address, set our ip and change callid */ +- ast_sip_ouraddrfor(&p->sa, &p->ourip, p); +- build_via(p); +- +- change_callid_pvt(p, NULL); +- + ast_cli(a->fd, "Sending NOTIFY of type '%s' to '%s'\n", a->argv[2], a->argv[i]); + sip_scheddestroy(p, SIP_TRANS_TIMEOUT); + transmit_invite(p, SIP_NOTIFY, 0, 2, NULL); + dialog_unref(p, "bump down the count of p since we're done with it."); + } + ++ ast_channel_release(chan); ++ ast_free(str); ++ ++ return CLI_SUCCESS; ++} ++ ++/*! \brief Enable/Disable DoNotDisturb on a peer */ ++static char *sip_cli_donotdisturb(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) ++{ ++ struct sip_peer *peer; ++ struct sip_alias *alias; ++ int donotdisturb; ++ ++ switch (cmd) { ++ case CLI_INIT: ++ e->command = "sip donotdisturb {on|off}"; ++ e->usage = ++ "Usage: sip donotdisturb {on|off} \n" ++ " Enables/Disables do not disturb on a SIP peer\n"; ++ return NULL; ++ case CLI_GENERATE: ++ if (a->pos == 3) { ++ return complete_sip_peer(a->word, a->n, 0); ++ } ++ return NULL; ++ } ++ ++ if (a->argc < 4) { ++ return CLI_SHOWUSAGE; ++ } ++ ++ if (!strcasecmp(a->argv[2], "on")) { ++ donotdisturb = 1; ++ } else if (!strcasecmp(a->argv[2], "off")) { ++ donotdisturb = 0; ++ } else { ++ return CLI_SHOWUSAGE; ++ } ++ ++ if (!(peer = sip_find_peer(a->argv[3], NULL, TRUE, FINDPEERS, FALSE, 0))) { ++ ast_cli(a->fd, "No such peer '%s'\n", a->argv[3]); ++ return CLI_FAILURE; ++ } ++ ++ ast_cli(a->fd, "Do Not Disturb on '%s' %s\n", peer->name, donotdisturb ? "enabled" : "disabled"); ++ peer->donotdisturb = donotdisturb; ++ ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", peer->name); ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (alias->peer) { ++ alias->peer->donotdisturb = peer->donotdisturb; ++ ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", alias->peer->name); ++ } ++ } ++ if (!peer->is_realtime) { ++ ast_db_put("SIP/DoNotDisturb", peer->name, donotdisturb ? "yes" : "no"); ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "donotdisturb", donotdisturb ? "yes" : "no", SENTINEL); ++ } ++ sip_send_donotdisturb(peer); ++ peer = sip_unref_peer(peer, "unref after sip_find_peer"); ++ ++ return CLI_SUCCESS; ++} ++ ++/*! \brief Login to/Logout from huntgroup for a peer */ ++static char *sip_cli_huntgroup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) ++{ ++ struct sip_peer *peer; ++ struct sip_alias *alias; ++ int huntgroup; ++ ++ switch (cmd) { ++ case CLI_INIT: ++ e->command = "sip huntgroup {on|off}"; ++ e->usage = ++ "Usage: sip huntgroup {on|off} \n" ++ " Login to/Logout from huntgroup for a SIP peer\n"; ++ return NULL; ++ case CLI_GENERATE: ++ if (a->pos == 3) { ++ return complete_sip_peer(a->word, a->n, 0); ++ } ++ return NULL; ++ } ++ ++ if (a->argc < 4) { ++ return CLI_SHOWUSAGE; ++ } ++ ++ if (!strcasecmp(a->argv[2], "on")) { ++ huntgroup = 1; ++ } else if (!strcasecmp(a->argv[2], "off")) { ++ huntgroup = 0; ++ } else { ++ return CLI_SHOWUSAGE; ++ } ++ ++ if (!(peer = sip_find_peer(a->argv[3], NULL, TRUE, FINDPEERS, FALSE, 0))) { ++ ast_cli(a->fd, "No such peer '%s'\n", a->argv[3]); ++ return CLI_FAILURE; ++ } ++ ++ ast_cli(a->fd, "Hunt Group %s for '%s'\n", huntgroup ? "login" : "logout", peer->name); ++ peer->huntgroup = huntgroup; ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (alias->peer) { ++ alias->peer->huntgroup = peer->huntgroup; ++ } ++ } ++ if (!peer->is_realtime) { ++ ast_db_put("SIP/HuntGroup", peer->name, huntgroup ? "yes" : "no"); ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "huntgroup", huntgroup ? "yes" : "no", SENTINEL); ++ } ++ sip_send_huntgroup(peer); ++ peer = sip_unref_peer(peer, "unref after sip_find_peer"); ++ ++ return CLI_SUCCESS; ++} ++ ++/*! \brief Sets/Removes the call fowarding extension for a peer */ ++static char *sip_cli_callforward(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) ++{ ++ struct sip_peer *peer; ++ const char *callforward; ++ ++ switch (cmd) { ++ case CLI_INIT: ++ e->command = "sip callforward {on|off}"; ++ e->usage = ++ "Usage: sip callforward {on |off }\n" ++ " Sets/Clears the call forwarding extension for a SIP peer\n"; ++ return NULL; ++ case CLI_GENERATE: ++ if (a->pos == 3) { ++ return complete_sip_peer(a->word, a->n, 0); ++ } ++ return NULL; ++ } ++ ++ if (a->argc < 4) { ++ return CLI_SHOWUSAGE; ++ } ++ ++ if (!strcasecmp(a->argv[2], "on")) { ++ if (a->argc < 5) { ++ return CLI_SHOWUSAGE; ++ } ++ callforward = a->argv[4]; ++ } else if (!strcasecmp(a->argv[2], "off")) { ++ callforward = ""; ++ } else { ++ return CLI_SHOWUSAGE; ++ } ++ ++ if (!(peer = sip_find_peer(a->argv[3], NULL, TRUE, FINDPEERS, FALSE, 0))) { ++ ast_cli(a->fd, "No such peer '%s'\n", a->argv[3]); ++ return CLI_FAILURE; ++ } ++ ++ if (ast_strlen_zero(callforward)) { ++ ast_cli(a->fd, "Call forwarding on '%s' cleared\n", peer->name); ++ } else { ++ ast_cli(a->fd, "Call forwarding on '%s' set to %s\n", peer->name, callforward); ++ } ++ ast_string_field_set(peer, callforward, callforward); ++ if (!peer->is_realtime) { ++ if (ast_strlen_zero(peer->callforward)) { ++ ast_db_del("SIP/CallForward", peer->name); ++ } else { ++ ast_db_put("SIP/CallForward", peer->name, callforward); ++ } ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "callforward", peer->callforward, SENTINEL); ++ } ++ sip_send_callforward(peer); ++ peer = sip_unref_peer(peer, "unref after sip_find_peer"); ++ + return CLI_SUCCESS; + } + +@@ -23424,7 +24717,7 @@ + }; + + /*! \brief ${SIPPEER()} Dialplan function - reads peer data */ +-static int function_sippeer(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) ++static int function_sippeer_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) + { + struct sip_peer *peer; + char *colname; +@@ -23478,7 +24771,7 @@ + ast_copy_string(buf, peer->useragent, len); + } else if (!strcasecmp(colname, "mailbox")) { + struct ast_str *mailbox_str = ast_str_alloca(512); +- peer_mailboxes_to_str(&mailbox_str, peer); ++ get_peer_mailboxes(&mailbox_str, peer); + ast_copy_string(buf, ast_str_buffer(mailbox_str), len); + } else if (!strcasecmp(colname, "context")) { + ast_copy_string(buf, peer->context, len); +@@ -23519,11 +24812,88 @@ + } else { + buf[0] = '\0'; + } ++ } else if (!strncasecmp(colname, "vmexten", 7)) { ++ ast_copy_string(buf, peer->vmexten, len); ++ } else if (!strncasecmp(colname, "donotdisturb", 12)) { ++ ast_copy_string(buf, peer->donotdisturb ? "yes" : "no", len); ++ } else if (!strncasecmp(colname, "callforward", 11)) { ++ ast_copy_string(buf, peer->callforward, len); ++ } else if (!strncasecmp(colname, "huntgroup", 9)) { ++ ast_copy_string(buf, peer->huntgroup ? "yes" : "no", len); ++ } else if (!strncasecmp(colname, "regcallid", 9)) { ++ ast_copy_string(buf, peer->regcallid, len); ++ } else if (!strncasecmp(colname, "ciscodevicename", 15)) { ++ ast_copy_string(buf, peer->cisco_devicename, len); ++ } else if (!strncasecmp(colname, "ciscolineindex", 14)) { ++ snprintf(buf, len, "%d", peer->cisco_lineindex); + } else { + buf[0] = '\0'; + } + +- sip_unref_peer(peer, "sip_unref_peer from function_sippeer, just before return"); ++ sip_unref_peer(peer, "sip_unref_peer from function_sippeer_read, just before return"); ++ ++ return 0; ++} ++ ++static int function_sippeer_write(struct ast_channel *chan, const char *cmd, char *data, const char *value) ++{ ++ struct sip_peer *peer; ++ struct sip_alias *alias; ++ char *colname; ++ ++ if ((colname = strchr(data, ','))) { ++ *colname++ = '\0'; ++ } else { ++ colname = ""; ++ } ++ ++ if (!(peer = sip_find_peer(data, NULL, TRUE, FINDPEERS, FALSE, 0))) { ++ return -1; ++ } ++ ++ if (!strncasecmp(colname, "donotdisturb", 12)) { ++ peer->donotdisturb = ast_true(value); ++ ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", peer->name); ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (alias->peer) { ++ alias->peer->donotdisturb = peer->donotdisturb; ++ ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", alias->peer->name); ++ } ++ } ++ if (!peer->is_realtime) { ++ ast_db_put("SIP/DoNotDisturb", peer->name, peer->donotdisturb ? "yes" : "no"); ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "donotdisturb", peer->donotdisturb ? "yes" : "no", SENTINEL); ++ } ++ sip_send_donotdisturb(peer); ++ } else if (!strncasecmp(colname, "huntgroup", 9)) { ++ peer->huntgroup = ast_true(value); ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (alias->peer) { ++ alias->peer->huntgroup = peer->huntgroup; ++ } ++ } ++ if (!peer->is_realtime) { ++ ast_db_put("SIP/HuntGroup", peer->name, peer->huntgroup ? "yes" : "no"); ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "huntgroup", peer->huntgroup ? "yes" : "no", SENTINEL); ++ } ++ sip_send_huntgroup(peer); ++ } else if (!strncasecmp(colname, "callforward", 11)) { ++ ast_string_field_set(peer, callforward, value); ++ if (!peer->is_realtime) { ++ if (ast_strlen_zero(peer->callforward)) { ++ ast_db_del("SIP/CallForward", peer->name); ++ } else { ++ ast_db_put("SIP/CallForward", peer->name, peer->callforward); ++ } ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "callforward", peer->callforward, SENTINEL); ++ } ++ sip_send_callforward(peer); ++ } ++ ++ sip_unref_peer(peer, "sip_unref_peer from function_sippeer_write, just before return"); + + return 0; + } +@@ -23531,7 +24901,8 @@ + /*! \brief Structure to declare a dialplan function: SIPPEER */ + static struct ast_custom_function sippeer_function = { + .name = "SIPPEER", +- .read = function_sippeer, ++ .read = function_sippeer_read, ++ .write = function_sippeer_write + }; + + /*! \brief update redirecting information for a channel based on headers +@@ -24131,6 +25502,35 @@ + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_NOT_CACHABLE, "SIP/%s", p->relatedpeer->name); + } + } ++ if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ const char *autoanswer = pbx_builtin_getvar_helper(p->owner, "CISCO_AUTOANSWER"); ++ ++ if (ast_true(autoanswer)) { ++ struct sip_pvt *ansp; ++ ++ if ((ansp = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ struct ast_str *content = ast_str_alloca(8192); ++ ++ copy_pvt_data(ansp, p); ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", p->callid); ++ ast_str_append(&content, 0, "%s\n", p->theirtag); ++ ast_str_append(&content, 0, "%s\n", p->tag); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ sip_pvt_lock(ansp); ++ transmit_refer_with_content(ansp, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); ++ sip_pvt_unlock(ansp); ++ dialog_unref(ansp, "bump down the count of pvt since we're done with it."); ++ } ++ } ++ } + } + if (find_sdp(req)) { + if (p->invitestate != INV_CANCELLED) { +@@ -24391,6 +25791,20 @@ + ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); + xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, TRUE); + sched_check_pendings(p); ++ ++ if (ast_test_flag(&p->flags[2], SIP_PAGE3_RELAY_NEAREND) || ast_test_flag(&p->flags[2], SIP_PAGE3_RELAY_FAREND)) { ++ if (ast_test_flag(&p->flags[2], SIP_PAGE3_RELAY_NEAREND)) { ++ ast_clear_flag(&p->flags[2], SIP_PAGE3_RELAY_NEAREND); ++ start_record_thread(p->join_callid, p->join_tag, p->join_theirtag, 0); ++ } else { ++ ast_clear_flag(&p->flags[2], SIP_PAGE3_RELAY_FAREND); ++ } ++ ast_set_flag(&p->flags[2], SIP_PAGE3_SDP_ACK); ++ transmit_invite(p, SIP_INVITE, FALSE, 1, NULL); ++ } else if (ast_test_flag(&p->flags[2], SIP_PAGE3_SDP_ACK)) { ++ ast_clear_flag(&p->flags[2], SIP_PAGE3_SDP_ACK); ++ ast_set_flag(&p->flags[2], SIP_PAGE3_CISCO_RECORDING); ++ } + break; + + case 407: /* Proxy authentication */ +@@ -24723,6 +26137,7 @@ + return; + + switch (resp) { ++ case 200: /* Out of Dialog REFER */ + case 202: /* Transfer accepted */ + /* We need to do something here */ + /* The transferee is now sending INVITE to target */ +@@ -24974,6 +26389,8 @@ + struct sip_peer *peer = /* sip_ref_peer( */ p->relatedpeer /* , "bump refcount on p, as it is being used in this function(handle_response_peerpoke)")*/ ; /* hope this is already refcounted! */ + int statechanged, is_reachable, was_reachable; + int pingtime = ast_tvdiff_ms(ast_tvnow(), peer->ps); ++ const char *status; ++ char lastms[20]; + + /* + * Compute the response time to a ping (goes in peer->lastms.) +@@ -24997,26 +26414,24 @@ + is_reachable = pingtime <= peer->maxms; + statechanged = peer->lastms == 0 /* yes, unknown before */ + || was_reachable != is_reachable; ++ status = is_reachable ? "Reachable" : "Lagged"; ++ snprintf(lastms, sizeof(lastms), "%d", pingtime); + + peer->lastms = pingtime; + peer->call = dialog_unref(peer->call, "unref dialog peer->call"); +- if (statechanged) { +- const char *s = is_reachable ? "Reachable" : "Lagged"; +- char str_lastms[20]; +- +- snprintf(str_lastms, sizeof(str_lastms), "%d", pingtime); + ++ if (statechanged) { + ast_log(LOG_NOTICE, "Peer '%s' is now %s. (%dms / %dms)\n", +- peer->name, s, pingtime, peer->maxms); ++ peer->name, status, pingtime, peer->maxms); + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); + if (sip_cfg.peer_rtupdate) { +- ast_update_realtime(ast_check_realtime("sipregs") ? "sipregs" : "sippeers", "name", peer->name, "lastms", str_lastms, SENTINEL); ++ ast_update_realtime(ast_check_realtime("sipregs") ? "sipregs" : "sippeers", "name", peer->name, "lastms", lastms, SENTINEL); + } + if (peer->endpoint) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + ast_endpoint_set_state(peer->endpoint, AST_ENDPOINT_ONLINE); + blob = ast_json_pack("{s: s, s: i}", +- "peer_status", s, ++ "peer_status", status, + "time", pingtime); + ast_endpoint_blob_publish(peer->endpoint, ast_endpoint_state_type(), blob); + } +@@ -25026,6 +26441,38 @@ + } + } + ++ if (!AST_LIST_EMPTY(&peer->aliases)) { ++ struct sip_alias *alias; ++ ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (!alias->peer) { ++ continue; ++ } ++ alias->peer->lastms = pingtime; ++ ++ if (statechanged) { ++ ast_log(LOG_NOTICE, "Peer '%s' is now %s. (%dms / %dms)\n", ++ alias->peer->name, status, pingtime, alias->peer->maxms); ++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); ++ if (sip_cfg.peer_rtupdate) { ++ ast_update_realtime("sippeers", "name", alias->peer->name, "lastms", lastms, SENTINEL); ++ } ++ if (alias->peer->endpoint) { ++ RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); ++ ast_endpoint_set_state(alias->peer->endpoint, AST_ENDPOINT_ONLINE); ++ blob = ast_json_pack("{s: s, s: i}", ++ "peer_status", status, ++ "time", pingtime); ++ ast_endpoint_blob_publish(alias->peer->endpoint, ast_endpoint_state_type(), blob); ++ } ++ ++ if (is_reachable && sip_cfg.regextenonqualify) { ++ register_peer_exten(alias->peer, TRUE); ++ } ++ } ++ } ++ } ++ + pvt_set_needdestroy(p, "got OPTIONS response"); + + /* Try again eventually */ +@@ -25558,6 +27005,9 @@ + + /* Wait for 487, then destroy */ + } else if (sipmethod == SIP_BYE) { ++ if (ast_test_flag(&p->flags[2], SIP_PAGE3_RTP_STATS_ON_BYE)) { ++ parse_rtp_stats(p, req); ++ } + pvt_set_needdestroy(p, "transaction completed"); + } + break; +@@ -25656,6 +27106,2239 @@ + return 0; + } + ++/*! \brief Handle idivert request */ ++static int handle_remotecc_idivert(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_pvt *targetcall_pvt; ++ struct ast_channel *chan, *bridged; ++ ++ /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ ++ if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { ++ ast_debug(1, "dialogid call leg does not exist\n"); ++ return -1; ++ } ++ ++ sip_pvt_lock(targetcall_pvt); ++ ++ if (!(chan = targetcall_pvt->owner)) { ++ ast_debug(1, "no owner channel\n"); ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ return -1; ++ } ++ ++ ast_channel_ref(chan); ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ if (ast_channel_state(chan) == AST_STATE_RINGING) { ++ ast_queue_control(chan, AST_CONTROL_BUSY); ++ } else if (ast_channel_state(chan) == AST_STATE_UP) { ++ if ((bridged = ast_channel_bridge_peer(chan))) { ++ pbx_builtin_setvar_helper(bridged, "IDIVERT_PEERNAME", peer->name); ++ ast_async_goto(bridged, peer->context, "idivert", 1); ++ ast_channel_unref(bridged); ++ } ++ } ++ ++ ast_channel_unref(chan); ++ ++ return 0; ++} ++ ++/*! \brief Handle hlog request */ ++static int handle_remotecc_hlog(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_alias *alias; ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ peer->huntgroup = !peer->huntgroup; ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (alias->peer) { ++ alias->peer->huntgroup = peer->huntgroup; ++ } ++ } ++ if (!peer->is_realtime) { ++ ast_db_put("SIP/HuntGroup", peer->name, peer->huntgroup ? "yes" : "no"); ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "huntgroup", peer->huntgroup ? "yes" : "no", SENTINEL); ++ } ++ sip_send_huntgroup(peer); ++ ++ return 0; ++} ++ ++/*! \brief destroy conference callback for ao2_alloc */ ++static void destroy_conference(void *obj) ++{ ++ struct sip_conference *conference = obj; ++ ++ ast_verb(3, "Destroying ad-hoc conference %d\n", conference->confid); ++ ++ if (conference->bridge) { ++ ast_bridge_destroy(conference->bridge, 0); ++ conference->bridge = NULL; ++ } ++ ++ AST_LIST_LOCK(&conferences); ++ AST_LIST_REMOVE(&conferences, conference, entry); ++ AST_LIST_UNLOCK(&conferences); ++} ++ ++/*! \brief create conference and assign it to a sip_pvt */ ++static int create_conference(struct sip_pvt *pvt) ++{ ++ struct sip_conference *conference; ++ ++ if (!(conference = ao2_alloc(sizeof(*conference), destroy_conference))) { ++ return -1; ++ } ++ ++ if (!(conference->bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_MULTIMIX, AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY, "SIP", "Conference", NULL))) { ++ ao2_ref(conference, -1); ++ return -1; ++ } ++ ++ ast_bridge_set_internal_sample_rate(conference->bridge, 8000); ++ ast_bridge_set_mixing_interval(conference->bridge, 20); ++ ++ ast_bridge_set_talker_src_video_mode(conference->bridge); ++ ++ conference->confid = ++next_confid; ++ conference->keep = ast_test_flag(&pvt->flags[2], SIP_PAGE3_CISCO_KEEP_CONFERENCE) ? 1 : 0; ++ conference->multiadmin = ast_test_flag(&pvt->flags[2], SIP_PAGE3_CISCO_MULTIADMIN_CONFERENCE) ? 1 : 0; ++ AST_LIST_HEAD_INIT_NOLOCK(&conference->participants); ++ ++ pvt->conference = conference; ++ ++ AST_LIST_LOCK(&conferences); ++ AST_LIST_INSERT_TAIL(&conferences, conference, entry); ++ AST_LIST_UNLOCK(&conferences); ++ ++ ast_verb(3, "Creating ad-hoc conference %d\n", conference->confid); ++ ++ return 0; ++} ++ ++static int talk_detector(struct ast_bridge_channel *chan, void *hook_pvt, int talking) ++{ ++ struct sip_participant *participant = hook_pvt; ++ struct sip_conference *conference = participant->conference; ++ ++ ast_debug(1, "%s %s talking in ad-hoc conference %d\n", ast_channel_name(participant->chan), talking ? "started" : "stopped", conference->confid); ++ participant->talking = talking; ++ ++ return 0; ++} ++ ++/*! \brief cleanup participant structure after leaving bridge */ ++static int leave_conference(struct ast_bridge_channel *chan, void *hook_pvt) ++{ ++ struct sip_participant *participant = hook_pvt; ++ struct sip_conference *conference = participant->conference; ++ ++ ast_verb(3, "%s left ad-hoc conference %d\n", ast_channel_name(participant->chan), conference->confid); ++ ++ ao2_lock(conference); ++ AST_LIST_REMOVE(&conference->participants, participant, entry); ++ if (participant->administrator) { ++ conference->administrators--; ++ } else { ++ conference->users--; ++ } ++ ao2_unlock(conference); ++ ++ if (conference->administrators + conference->users > 1) { ++ struct sip_participant *participant; ++ ++ AST_LIST_TRAVERSE(&conference->participants, participant, entry) { ++ ast_bridge_channel_queue_playfile(ast_channel_internal_bridge_channel(participant->chan), NULL, "confbridge-leave", NULL); ++ } ++ } ++ ++ if (conference->administrators + conference->users == 1) { ++ struct sip_participant *participant; ++ ++ ast_verb(3, "Only one participant left in ad-hoc conference %d, removing.\n", conference->confid); ++ ++ participant = AST_LIST_FIRST(&conference->participants); ++ ast_bridge_remove(conference->bridge, participant->chan); ++ } else if (conference->users && !conference->administrators && !conference->keep) { ++ struct sip_participant *participant; ++ ++ ast_verb(3, "No more administrators in ad-hoc conference %d\n", conference->confid); ++ ++ AST_LIST_TRAVERSE(&conference->participants, participant, entry) { ++ ast_bridge_remove(conference->bridge, participant->chan); ++ } ++ } ++ ++ ast_channel_unref(participant->chan); ++ ao2_ref(participant->conference, -1); ++ ast_free(participant); ++ ++ return -1; ++} ++ ++/*! \brief allocate participant structure and move channel into conference bridge */ ++static int join_conference(struct sip_conference *conference, struct ast_channel *chan, int administrator) ++{ ++ struct sip_participant *participant; ++ struct ast_bridge_channel *bridgechan; ++ ++ if (!administrator && conference->multiadmin) { ++ ast_channel_lock(chan); ++ if (IS_SIP_TECH(ast_channel_tech(chan))) { ++ struct sip_pvt *pvt = ast_channel_tech_pvt(chan); ++ ++ sip_pvt_lock(pvt); ++ if (ast_test_flag(&pvt->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && ast_test_flag(&pvt->flags[2], SIP_PAGE3_CISCO_MULTIADMIN_CONFERENCE)) { ++ ao2_ref(conference, +1); ++ pvt->conference = conference; ++ administrator = 1; ++ } ++ sip_pvt_unlock(pvt); ++ } ++ ast_channel_unlock(chan); ++ } ++ ++ if (!(participant = ast_calloc(1, sizeof(*participant)))) { ++ return -1; ++ } ++ ++ ast_channel_ref(chan); ++ participant->chan = chan; ++ ++ ao2_ref(conference, +1); ++ participant->conference = conference; ++ ++ participant->callid = ++conference->next_callid; ++ participant->administrator = administrator; ++ ++ bridgechan = ast_channel_internal_bridge_channel(chan); ++ ao2_ref(bridgechan, +1); ++ ++ bridgechan->inhibit_colp = 1; ++ ++ if (ast_bridge_move(conference->bridge, ast_channel_internal_bridge(chan), chan, NULL, 0)) { ++ ao2_ref(bridgechan, -1); ++ ao2_ref(conference, -1); ++ ast_channel_unref(chan); ++ ast_free(participant); ++ return -1; ++ } ++ ++ ast_bridge_features_remove(bridgechan->features, AST_BRIDGE_HOOK_REMOVE_ON_PULL); ++ ast_bridge_leave_hook(bridgechan->features, leave_conference, participant, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL); ++ ast_bridge_talk_detector_hook(bridgechan->features, talk_detector, participant, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL); ++ ao2_ref(bridgechan, -1); ++ ++ ao2_lock(conference); ++ AST_LIST_INSERT_HEAD(&conference->participants, participant, entry); ++ if (administrator) { ++ conference->administrators++; ++ } else { ++ conference->users++; ++ } ++ ao2_unlock(conference); ++ ++ if (conference->administrators + conference->users > 2) { ++ struct sip_participant *participant; ++ ++ AST_LIST_TRAVERSE(&conference->participants, participant, entry) { ++ ast_bridge_channel_queue_playfile(ast_channel_internal_bridge_channel(participant->chan), NULL, "confbridge-join", NULL); ++ } ++ } ++ ++ ast_verb(3, "%s joined ad-hoc conference %d\n", ast_channel_name(chan), conference->confid); ++ ++ if (administrator) { ++ struct ast_party_connected_line connected; ++ ++ ast_party_connected_line_init(&connected); ++ ++ connected.id.name.str = ast_strdup("Conference"); ++ connected.id.name.valid = 1; ++ ++ connected.id.number.str = ast_strdup(""); ++ connected.id.number.valid = 1; ++ ++ connected.id.name.presentation = connected.id.number.presentation = AST_PRES_ALLOWED | AST_PRES_USER_NUMBER_PASSED_SCREEN; ++ connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_CONFERENCE; ++ ++ ast_channel_update_connected_line(chan, &connected, NULL); ++ ast_party_connected_line_free(&connected); ++ } ++ ++ return 0; ++} ++ ++/*! \brief add channels to conference */ ++static void *conference_thread(void *obj) ++{ ++ struct conference_data *conference_data = obj; ++ struct sip_pvt *pvt; ++ struct ast_channel *chan, *bridged; ++ struct sip_conference *conference = NULL; ++ struct ast_str *content = ast_str_alloca(8192); ++ int res = -1; ++ ++ if (!(pvt = get_sip_pvt(conference_data->callid, conference_data->tag, conference_data->theirtag))) { ++ ast_debug(1, "call leg does not exist\n"); ++ goto conference_cleanup; ++ } ++ ++ sip_pvt_lock(pvt); ++ ++ /* Is this a new ad-hoc conference? */ ++ if (!pvt->conference) { ++ if (create_conference(pvt)) { ++ ast_debug(1, "unable to create conference\n"); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ goto conference_cleanup; ++ } ++ ++ /* Increase ref on conference so we don't need to keep a ref on it's parent dialog */ ++ conference = pvt->conference; ++ ao2_ref(conference, +1); ++ ++ if (!(chan = pvt->owner)) { ++ ast_debug(1, "no owner channel\n"); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ goto conference_cleanup; ++ } ++ ++ ast_channel_ref(chan); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ ++ if (!(bridged = ast_channel_bridge_peer(chan))) { ++ ast_debug(1, "no bridged channel\n"); ++ ast_channel_unref(chan); ++ goto conference_cleanup; ++ } ++ ++ if (join_conference(conference, chan, 1)) { ++ ast_channel_unref(chan); ++ ast_channel_unref(bridged); ++ goto conference_cleanup; ++ } ++ ++ ast_indicate(bridged, AST_CONTROL_UNHOLD); ++ ++ if (join_conference(conference, bridged, 0)) { ++ ast_channel_unref(chan); ++ ast_channel_unref(bridged); ++ goto conference_cleanup; ++ } ++ ++ ast_channel_unref(chan); ++ ast_channel_unref(bridged); ++ } else { ++ conference = pvt->conference; ++ ao2_ref(conference, +1); ++ ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ } ++ ++ if (!conference_data->joining) { ++ if (!(pvt = get_sip_pvt(conference_data->join_callid, conference_data->join_tag, conference_data->join_theirtag))) { ++ ast_debug(1, "join call leg does not exist\n"); ++ goto conference_cleanup; ++ } ++ ++ sip_pvt_lock(pvt); ++ ++ if (!(chan = pvt->owner)) { ++ ast_debug(1, "no owner channel\n"); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ goto conference_cleanup; ++ } ++ ++ ast_channel_ref(chan); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ ++ if (!(bridged = ast_channel_bridge_peer(chan))) { ++ ast_debug(1, "no bridged channel\n"); ++ ast_channel_unref(chan); ++ goto conference_cleanup; ++ } ++ ++ ast_indicate(bridged, AST_CONTROL_UNHOLD); ++ ++ if (join_conference(conference, bridged, 0)) { ++ ast_channel_unref(chan); ++ ast_channel_unref(bridged); ++ goto conference_cleanup; ++ } ++ ++ ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); ++ ++ ast_channel_unref(chan); ++ ast_channel_unref(bridged); ++ ++ res = 0; ++ ++ /* We need to signal to the phone to take the first call leg off hold, even though the generator on that ++ channel has gone due to the masquerade as the phone still thinks that it is on hold */ ++ if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ goto conference_cleanup; ++ } ++ copy_pvt_data(pvt, conference_data->pvt); ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", conference_data->callid); ++ ast_str_append(&content, 0, "%s\n", conference_data->theirtag); ++ ast_str_append(&content, 0, "%s\n", conference_data->tag); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ sip_pvt_lock(pvt); ++ transmit_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ } else { ++ struct sip_selected *selected; ++ ++ AST_LIST_TRAVERSE(&conference_data->selected, selected, entry) { ++ /* Skip the join dialog as that was added to the conference above */ ++ if (!strcmp(conference_data->callid, selected->callid) && !strcmp(conference_data->tag, selected->tag) && !strcmp(conference_data->theirtag, selected->theirtag)) { ++ continue; ++ } ++ ++ if (!(pvt = get_sip_pvt(selected->callid, selected->tag, selected->theirtag))) { ++ ast_debug(1, "call leg does not exist\n"); ++ continue; ++ } ++ ++ sip_pvt_lock(pvt); ++ ++ if (!(chan = pvt->owner)) { ++ ast_debug(1, "no owner channel\n"); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ goto conference_cleanup; ++ } ++ ++ ast_channel_ref(chan); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ ++ if (!(bridged = ast_channel_bridge_peer(chan))) { ++ ast_debug(1, "no bridged channel\n"); ++ ast_channel_unref(chan); ++ goto conference_cleanup; ++ } ++ ++ ast_indicate(bridged, AST_CONTROL_UNHOLD); ++ ++ if (join_conference(conference, bridged, 0)) { ++ ast_channel_unref(chan); ++ ast_channel_unref(bridged); ++ goto conference_cleanup; ++ } ++ ++ ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); ++ ++ ast_channel_unref(chan); ++ ast_channel_unref(bridged); ++ } ++ ++ res = 0; ++ } ++ ++conference_cleanup: ++ if (conference) { ++ ao2_ref(conference, -1); ++ } ++ ++ ast_str_reset(content); ++ ++ if (!conference_data->joining) { ++ struct sip_request req; ++ ++ sip_pvt_lock(conference_data->pvt); ++ reqprep(&req, conference_data->pvt, SIP_NOTIFY, 0, 1); ++ add_header(&req, "Event", "refer"); ++ add_header(&req, "Subscription-State", "terminated;reason=noresource"); ++ add_header(&req, "Content-Type", "application/x-cisco-remotecc-response+xml"); ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%d\n", res ? 500 : 200); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ add_content(&req, ast_str_buffer(content)); ++ send_request(conference_data->pvt, &req, XMIT_RELIABLE, conference_data->pvt->ocseq); ++ sip_pvt_unlock(conference_data->pvt); ++ } else { ++ if ((pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ copy_pvt_data(pvt, conference_data->pvt); ++ ++ if (res) { ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "notify_display\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", conference_data->callid); ++ ast_str_append(&content, 0, "%s\n", conference_data->theirtag); ++ ast_str_append(&content, 0, "%s\n", conference_data->tag); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\200S\n"); ++ ast_str_append(&content, 0, "10\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "1\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ } else { ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", conference_data->callid); ++ ast_str_append(&content, 0, "%s\n", conference_data->theirtag); ++ ast_str_append(&content, 0, "%s\n", conference_data->tag); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Join\n"); ++ ast_str_append(&content, 0, "Complete\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ } ++ ++ sip_pvt_lock(pvt); ++ transmit_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ } ++ } ++ ++ if (conference_data->joining) { ++ struct sip_selected *selected; ++ ++ while ((selected = AST_LIST_REMOVE_HEAD(&conference_data->selected, entry))) { ++ destroy_selected(selected); ++ } ++ } ++ ++ dialog_unref(conference_data->pvt, "drop conference_data->pvt"); ++ ast_string_field_free_memory(conference_data); ++ ast_free(conference_data); ++ ++ return NULL; ++} ++ ++/*! \brief Handle conference request */ ++static int handle_remotecc_conference(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ pthread_t threadid; ++ char tmp[64]; ++ struct sip_request notify_req; ++ struct conference_data *conference_data; ++ struct sip_selected *selected; ++ ++ if (!(conference_data = ast_calloc_with_stringfields(1, struct conference_data, 128))) { ++ return -1; ++ } ++ ++ dialog_ref(pvt, "copying dialog pvt into conference_data struct"); ++ conference_data->pvt = pvt; ++ conference_data->joining = !strcmp(remotecc_data->softkeyevent, "Join"); ++ ++ ast_string_field_set(conference_data, callid, remotecc_data->dialogid.callid); ++ ast_string_field_set(conference_data, tag, remotecc_data->dialogid.remotetag); ++ ast_string_field_set(conference_data, theirtag, remotecc_data->dialogid.localtag); ++ ++ if (!conference_data->joining) { ++ ast_string_field_set(conference_data, join_callid, remotecc_data->consultdialogid.callid); ++ ast_string_field_set(conference_data, join_tag, remotecc_data->consultdialogid.remotetag); ++ ast_string_field_set(conference_data, join_theirtag, remotecc_data->consultdialogid.localtag); ++ } else { ++ ao2_lock(peer); ++ while ((selected = AST_LIST_REMOVE_HEAD(&peer->selected, entry))) { ++ AST_LIST_INSERT_TAIL(&conference_data->selected, selected, entry); ++ } ++ ao2_unlock(peer); ++ } ++ ++ if (ast_pthread_create_detached_background(&threadid, NULL, conference_thread, conference_data)) { ++ dialog_unref(conference_data->pvt, "thread creation failed"); ++ ast_string_field_free_memory(conference_data); ++ ast_free(conference_data); ++ return -1; ++ } ++ ++ /* If the conference fails we send back a NOTIFY telling the phone */ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ if (!conference_data->joining) { ++ ast_set_flag(&pvt->flags[0], SIP_OUTGOING); ++ ast_set_flag(&pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); ++ ++ pvt->subscribed = REMOTECC_XML; ++ pvt->expiry = min_expiry; ++ ++ copy_request(&pvt->initreq, req); ++ initreqprep(¬ify_req, pvt, SIP_NOTIFY, NULL); ++ add_header(¬ify_req, "Event", "refer"); ++ snprintf(tmp, sizeof(tmp), "active;expires=%d", pvt->expiry); ++ add_header(¬ify_req, "Subscription-State", tmp); ++ send_request(pvt, ¬ify_req, XMIT_RELIABLE, pvt->ocseq); ++ } ++ ++ return 0; ++} ++ ++/*! \brief Handle conflist and confdetails requests */ ++static int handle_remotecc_conflist(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_pvt *refer_pvt; ++ struct sip_conference *conference = NULL; ++ struct sip_participant *participant; ++ struct ast_str *content; ++ int is79xx = strstr(sip_get_header(req, "User-Agent"), "CP79") ? 1 : 0; ++ ++ if (!ast_strlen_zero(remotecc_data->dialogid.callid)) { ++ struct sip_pvt *targetcall_pvt; ++ ++ if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { ++ ast_debug(1, "dialogid call leg does not exist\n"); ++ return -1; ++ } ++ ++ sip_pvt_lock(targetcall_pvt); ++ ++ if ((conference = targetcall_pvt->conference)) { ++ ao2_ref(conference, +1); ++ } ++ ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop pvt"); ++ } else if (remotecc_data->confid) { ++ AST_LIST_LOCK(&conferences); ++ AST_LIST_TRAVERSE(&conferences, conference, entry) { ++ if (conference->confid == remotecc_data->confid) { ++ ao2_ref(conference, +1); ++ break; ++ } ++ } ++ AST_LIST_UNLOCK(&conferences); ++ } ++ ++ if (!conference) { ++ ast_debug(1, "Unable to find conference\n"); ++ return -1; ++ } ++ ++ if (!ast_strlen_zero(remotecc_data->usercalldata) && strcmp(remotecc_data->usercalldata, "Update")) { ++ char softkey[16]; ++ int callid; ++ ++ if (!strcmp(remotecc_data->usercalldata, "Remove") || !strcmp(remotecc_data->usercalldata, "Mute")) { ++ ast_string_field_set(peer, cisco_softkey, remotecc_data->usercalldata); ++ ao2_ref(conference, -1); ++ transmit_response(pvt, "202 Accepted", req); ++ return 0; ++ } ++ ++ /* Default action is to mute/unmute */ ++ ast_copy_string(softkey, S_OR(peer->cisco_softkey, "Mute"), sizeof(softkey)); ++ ast_string_field_set(peer, cisco_softkey, ""); ++ callid = atoi(remotecc_data->usercalldata); ++ ++ ao2_lock(conference); ++ AST_LIST_TRAVERSE(&conference->participants, participant, entry) { ++ if (participant->callid == callid) { ++ if (!strcmp(softkey, "Remove")) { ++ ast_verb(3, "%s is being removed from ad-hoc conference %d\n", ++ ast_channel_name(participant->chan), conference->confid); ++ ++ ast_bridge_remove(conference->bridge, participant->chan); ++ participant->removed = 1; ++ } else if (!strcmp(softkey, "Mute")) { ++ ast_verb(3, "%s is being %s in ad-hoc conference %d\n", ++ ast_channel_name(participant->chan), participant->muted ? "unmuted" : "muted", conference->confid); ++ participant->muted = !participant->muted; ++ ++ ast_channel_lock(participant->chan); ++ ast_channel_internal_bridge_channel(participant->chan)->features->mute = participant->muted; ++ ast_channel_unlock(participant->chan); ++ } ++ break; ++ } ++ } ++ ao2_unlock(conference); ++ } ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ if (!(content = ast_str_create(8192))) { ++ ao2_ref(conference, -1); ++ return 0; ++ } ++ ++ if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ ao2_ref(conference, -1); ++ ast_free(content); ++ return 0; ++ } ++ copy_pvt_data(refer_pvt, pvt); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%d\n", REMOTECC_CONFLIST); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "StationSequenceLast\n"); ++ ast_str_append(&content, 0, "2\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "%d\n", conference->confid); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Conference\n"); ++ ++ ao2_lock(conference); ++ AST_LIST_TRAVERSE(&conference->participants, participant, entry) { ++ char *status, *callerid = NULL; ++ ++ if (participant->removed) { ++ continue; ++ } ++ ++ if (participant->muted) { ++ status = "- "; ++ } else if (participant->talking) { ++ status = "+ "; ++ } else { ++ status = ""; ++ } ++ ++ ast_channel_lock(participant->chan); ++ if (ast_strlen_zero(callerid) && ast_channel_caller(participant->chan)->id.name.valid) { ++ callerid = ast_strdupa(ast_channel_caller(participant->chan)->id.name.str); ++ } ++ if (ast_strlen_zero(callerid) && ast_channel_caller(participant->chan)->id.number.valid) { ++ callerid = ast_strdupa(ast_channel_caller(participant->chan)->id.number.str); ++ } ++ ast_channel_unlock(participant->chan); ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s%s\n", status, S_OR(callerid, CALLERID_UNKNOWN)); ++ ast_str_append(&content, 0, "UserCallData:%d:0:%d:0:%d\n", REMOTECC_CONFLIST, conference->confid, participant->callid); ++ ast_str_append(&content, 0, "\n"); ++ ++ ast_channel_unlock(participant->chan); ++ } ++ ao2_unlock(conference); ++ ++ ast_str_append(&content, 0, "Please select\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Exit\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 3 : 1); ++ ast_str_append(&content, 0, "SoftKey:Exit\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Remove\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 1 : 2); ++ ast_str_append(&content, 0, "UserCallDataSoftKey:Select:%d:0:%d:0:Remove\n", REMOTECC_CONFLIST, conference->confid); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Mute\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 2 : 3); ++ ast_str_append(&content, 0, "UserCallDataSoftKey:Select:%d:0:%d:0:Mute\n", REMOTECC_CONFLIST, conference->confid); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Update\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 4 : 4); ++ ast_str_append(&content, 0, "UserCallDataSoftKey:Update:%d:0:%d:0:Update\n", REMOTECC_CONFLIST, conference->confid); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(refer_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); ++ ao2_ref(conference, -1); ++ ast_free(content); ++ ++ return 0; ++} ++ ++/*! \brief Handle remove last conference participant requests */ ++static int handle_remotecc_rmlastconf(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_pvt *targetcall_pvt; ++ struct sip_conference *conference = NULL; ++ struct sip_participant *participant; ++ ++ if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { ++ ast_debug(1, "dialogid call leg does not exist\n"); ++ return -1; ++ } ++ ++ sip_pvt_lock(targetcall_pvt); ++ ++ if (targetcall_pvt->conference) { ++ conference = targetcall_pvt->conference; ++ ao2_ref(conference, +1); ++ } ++ ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ ++ if (!conference) { ++ ast_debug(1, "Not in a conference\n"); ++ return -1; ++ } ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ ao2_lock(conference); ++ if ((participant = AST_LIST_FIRST(&conference->participants))) { ++ ast_bridge_remove(conference->bridge, participant->chan); ++ } ++ ao2_unlock(conference); ++ ao2_ref(conference, -1); ++ ++ return 0; ++} ++ ++static void remotecc_park_notify(struct park_data *park_data, enum ast_parked_call_event_type event_type, int parkingspace, long unsigned int timeout) ++{ ++ struct ast_str *content = ast_str_alloca(8192); ++ ++ if (park_data->monitor) { ++ struct sip_request req; ++ const char *fromdomain; ++ char *parkevent; ++ ++ if (event_type == PARKED_CALL) { ++ parkevent = "parked"; ++ } else if (event_type == PARKED_CALL_REMINDER) { ++ parkevent = "reminder"; ++ } else if (event_type == PARKED_CALL_UNPARKED) { ++ parkevent = "retrieved"; ++ } else if (event_type == PARKED_CALL_TIMEOUT) { ++ parkevent = "forwarded"; ++ } else if (event_type == PARKED_CALL_GIVEUP) { ++ parkevent = "abandoned"; ++ } else if (event_type == PARKED_CALL_FAILED) { ++ parkevent = "error"; ++ } else { ++ return; ++ } ++ ++ sip_pvt_lock(park_data->pvt); ++ ++ if (!ast_test_flag(&park_data->pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED)) { ++ ast_set_flag(&park_data->pvt->flags[0], SIP_OUTGOING); ++ ast_set_flag(&park_data->pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); ++ ++ park_data->pvt->subscribed = DIALOG_INFO_XML; ++ park_data->pvt->expiry = timeout; ++ ++ initreqprep(&req, park_data->pvt, SIP_NOTIFY, NULL); ++ } else { ++ reqprep(&req, park_data->pvt, SIP_NOTIFY, 0, 1); ++ } ++ park_data->pvt->dialogver++; ++ ++ add_header(&req, "Event", "refer"); ++ if (event_type == PARKED_CALL || event_type == PARKED_CALL_REMINDER) { ++ char tmp[64]; ++ ++ snprintf(tmp, sizeof(tmp), "active;expires=%d", park_data->pvt->expiry); ++ add_header(&req, "Subscription-State", tmp); ++ } else { ++ add_header(&req, "Subscription-State", "terminated;reason=noresource"); ++ } ++ add_header(&req, "Content-Type", "application/dialog-info+xml"); ++ fromdomain = S_OR(park_data->pvt->fromdomain, ast_sockaddr_stringify_host_remote(&park_data->pvt->ourip)); ++ ++ ast_str_append(&content, 0, "\n"); ++ /* "parmams" is a typo in the the Cisco API, duh. */ ++ ast_str_append(&content, 0, "\n", park_data->pvt->dialogver, parkingspace, fromdomain); ++ ast_str_append(&content, 0, "\n", parkingspace); ++ if (event_type == PARKED_CALL || event_type == PARKED_CALL_REMINDER) { ++ ast_str_append(&content, 0, "confirmed\n"); ++ } else { ++ ast_str_append(&content, 0, "terminated\n"); ++ } ++ ast_str_append(&content, 0, "%s\n", parkevent); ++ ast_str_append(&content, 0, "sip:%d@%s\n", parkingspace, fromdomain); ++ ast_str_append(&content, 0, "sip:%d@%s\n", parkingspace, fromdomain); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ add_content(&req, ast_str_buffer(content)); ++ send_request(park_data->pvt, &req, XMIT_RELIABLE, park_data->pvt->ocseq++); ++ sip_pvt_unlock(park_data->pvt); ++ } else { ++ struct sip_pvt *pvt; ++ ++ if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return; ++ } ++ copy_pvt_data(pvt, park_data->pvt); ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "notify_display\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", park_data->callid); ++ ast_str_append(&content, 0, "%s\n", park_data->theirtag); ++ ast_str_append(&content, 0, "%s\n", park_data->tag); ++ ast_str_append(&content, 0, "\n"); ++ if (event_type == PARKED_CALL) { ++ ast_str_append(&content, 0, "\200! %d\n", parkingspace); ++ } else { ++ ast_str_append(&content, 0, "\200^\n"); ++ } ++ ast_str_append(&content, 0, "10\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "1\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ sip_pvt_lock(pvt); ++ transmit_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ } ++} ++ ++static void park_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) ++{ ++ struct park_data *park_data = data; ++ struct ast_parked_call_payload *payload; ++ ++ if (stasis_message_type(message) != ast_parked_call_type()) { ++ return; ++ } ++ ++ if (!stasis_subscription_final_message(sub, message)) { ++ payload = stasis_message_data(message); ++ ++ if (strcmp(park_data->uniqueid, payload->parkee->base->uniqueid)) { ++ return; ++ } ++ ++ /* Send notification before hanging up the call so the dialog still exists on the phone */ ++ remotecc_park_notify(park_data, payload->event_type, payload->parkingspace, payload->timeout); ++ ++ if (payload->event_type == PARKED_CALL) { ++ ast_softhangup(park_data->chan, AST_SOFTHANGUP_EXPLICIT); ++ ast_channel_unref(park_data->chan); ++ park_data->chan = NULL; ++ } ++ ++ if (park_data->monitor && (payload->event_type == PARKED_CALL || payload->event_type == PARKED_CALL_REMINDER)) { ++ return; ++ } ++ } ++ ++ stasis_unsubscribe(sub); ++ ast_channel_cleanup(park_data->chan); ++ ++ dialog_unref(park_data->pvt, "drop park_data->pvt"); ++ ast_string_field_free_memory(park_data); ++ ast_free(park_data); ++} ++ ++/*! \brief park call */ ++static void *park_thread(void *obj) ++{ ++ struct park_data *park_data = obj; ++ struct sip_pvt *pvt; ++ struct ast_channel *chan, *bridged; ++ struct stasis_subscription *sub = NULL; ++ struct ast_exten *exten; ++ struct pbx_find_info find_info = { .stacklen = 0 }; ++ int res = -1; ++ ++ if (!(pvt = get_sip_pvt(park_data->callid, park_data->tag, park_data->theirtag))) { ++ ast_debug(1, "call leg does not exist\n"); ++ goto park_cleanup; ++ } ++ ++ sip_pvt_lock(pvt); ++ ++ if (!(chan = pvt->owner)) { ++ ast_debug(1, "no owner channel\n"); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ goto park_cleanup; ++ } ++ ++ ast_channel_ref(chan); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ ++ if (!(bridged = ast_channel_bridge_peer(chan))) { ++ ast_debug(1, "no bridge channel"); ++ ast_channel_unref(chan); ++ goto park_cleanup; ++ } ++ ++ /* needed so that comebacktoorigin will work */ ++ pbx_builtin_setvar_helper(bridged, "BLINDTRANSFER", ast_channel_name(chan)); ++ pbx_builtin_setvar_helper(bridged, "PARKINGLOT", ast_channel_parkinglot(chan)); ++ ++ park_data->chan = chan; ++ ast_channel_ref(chan); ++ ast_string_field_set(park_data, uniqueid, ast_channel_uniqueid(bridged)); ++ ++ sub = stasis_subscribe(ast_parking_topic(), park_stasis_cb, park_data); ++ ++ exten = pbx_find_extension(NULL, NULL, &find_info, park_data->context, "park", 1, NULL, NULL, E_MATCH); ++ ast_bridge_channel_write_park(ast_channel_internal_bridge_channel(chan), ++ ast_channel_uniqueid(bridged), ast_channel_uniqueid(chan), exten ? ast_get_extension_app_data(exten) : NULL); ++ ++ ast_channel_unref(chan); ++ ast_channel_unref(bridged); ++ ++ transmit_response(park_data->pvt, "202 Accepted", &park_data->pvt->initreq); ++ res = 0; ++ ++park_cleanup: ++ if (res) { ++ transmit_response(park_data->pvt, "503 Service Unavailable", &park_data->pvt->initreq); ++ remotecc_park_notify(park_data, PARKED_CALL_FAILED, 0, 0); ++ ++ if (sub) { ++ stasis_unsubscribe(sub); ++ } ++ if (park_data->chan) { ++ ast_channel_unref(park_data->chan); ++ } ++ dialog_unref(park_data->pvt, "drop park_data->pvt"); ++ ast_string_field_free_memory(park_data); ++ ast_free(park_data); ++ } ++ ++ return NULL; ++} ++ ++/*! \brief Handle remotecc park requests */ ++static int handle_remotecc_park(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ pthread_t threadid; ++ struct park_data *park_data; ++ ++ if (!(park_data = ast_calloc_with_stringfields(1, struct park_data, 128))) { ++ return -1; ++ } ++ ++ dialog_ref(pvt, "copying dialog pvt into park_data struct"); ++ park_data->pvt = pvt; ++ copy_request(&pvt->initreq, req); ++ park_data->monitor = !strcmp(remotecc_data->softkeyevent, "ParkMonitor"); ++ ++ ast_string_field_set(park_data, context, peer->context); ++ ast_string_field_set(park_data, callid, remotecc_data->dialogid.callid); ++ ast_string_field_set(park_data, tag, remotecc_data->dialogid.remotetag); ++ ast_string_field_set(park_data, theirtag, remotecc_data->dialogid.localtag); ++ ++ if (ast_pthread_create_detached_background(&threadid, NULL, park_thread, park_data)) { ++ dialog_unref(park_data->pvt, "thread creation failed"); ++ ast_string_field_free_memory(park_data); ++ ast_free(park_data); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++/*! \brief Handle remotecc parkmonitor requests */ ++static int handle_remotecc_parkmonitor(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ return handle_remotecc_park(pvt, req, peer, remotecc_data); ++} ++ ++static int wait_for_recording(void *obj) ++{ ++ struct sip_pvt *pvt = obj; ++ ++ sip_pvt_lock(pvt); ++ if (ast_channel_state(pvt->owner) != AST_STATE_UP) { ++ sip_pvt_unlock(pvt); ++ return -1; ++ } ++ ++ if (!ast_test_flag(&pvt->flags[2], SIP_PAGE3_CISCO_RECORDING)) { ++ sip_pvt_unlock(pvt); ++ return -1; ++ } ++ ++ sip_pvt_unlock(pvt); ++ return 0; ++} ++ ++static void *record_thread(void *obj) ++{ ++ struct record_data *record_data = obj; ++ struct sip_pvt *pvt, *targetcall_pvt; ++ struct ast_channel *chan; ++ struct ast_format_cap *cap; ++ struct ast_party_connected_line connected; ++ char *peername, *uniqueid, *channame; ++ int cause; ++ ++ if (!(targetcall_pvt = get_sip_pvt(record_data->callid, record_data->tag, record_data->theirtag))) { ++ ast_debug(1, "call leg does not exist\n"); ++ goto record_cleanup; ++ } ++ ++ sip_pvt_lock(targetcall_pvt); ++ ++ if (!(chan = targetcall_pvt->owner)) { ++ ast_debug(1, "no owner channel\n"); ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ goto record_cleanup; ++ } ++ ++ peername = ast_strdupa(targetcall_pvt->peername); ++ channame = ast_strdupa(ast_channel_name(chan)); ++ uniqueid = ast_strdupa(ast_channel_uniqueid(chan)); ++ ++ cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); ++ ast_format_cap_append(cap, ast_channel_readformat(chan), 0); ++ ++ sip_pvt_unlock(targetcall_pvt); ++ ++ if (!(chan = ast_request("SIP", cap, NULL, NULL, peername, &cause))) { ++ ast_debug(1, "unable to request channel\n"); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ goto record_cleanup; ++ } ++ ++ ao2_ref(cap, -1); ++ pvt = ast_channel_tech_pvt(chan); ++ ++ ast_string_field_set(pvt, join_callid, record_data->callid); ++ ast_string_field_set(pvt, join_tag, record_data->tag); ++ ast_string_field_set(pvt, join_theirtag, record_data->theirtag); ++ ++ ast_set_flag(&pvt->flags[1], SIP_PAGE2_CALL_ONHOLD_INACTIVE); ++ ast_set_flag(&pvt->flags[2], record_data->outgoing ? SIP_PAGE3_RELAY_NEAREND : SIP_PAGE3_RELAY_FAREND); ++ ++ /* We are abusing the onhold flags to set the inactive attribute in the SDP, bump the onhold counter ++ because when recording starts the reinvite code will decrement onhold when those flags are cleared */ ++ if (pvt->relatedpeer) { ++ ast_atomic_fetchadd_int(&pvt->relatedpeer->onhold, +1); ++ } ++ ++ ast_party_connected_line_set_init(&connected, ast_channel_connected(chan)); ++ connected.id.name.valid = 1; ++ connected.id.name.str = "Record"; ++ ast_channel_set_connected_line(chan, &connected, NULL); ++ ++ ast_channel_context_set(chan, pvt->context); ++ ast_channel_exten_set(chan, "record"); ++ ast_channel_priority_set(chan, 1); ++ ++ pbx_builtin_setvar_helper(chan, "RECORD_PEERNAME", peername); ++ pbx_builtin_setvar_helper(chan, "RECORD_UNIQUEID", uniqueid); ++ pbx_builtin_setvar_helper(chan, "RECORD_CHANNEL", channame); ++ pbx_builtin_setvar_helper(chan, "RECORD_DIRECTION", record_data->outgoing ? "out" : "in"); ++ ++ if (ast_call(chan, peername, 5000)) { ++ ast_debug(1, "unable to call\n"); ++ ast_hangup(chan); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ goto record_cleanup; ++ } ++ ++ if (ast_safe_sleep_conditional(chan, 5000, wait_for_recording, pvt)) { ++ ast_debug(1, "no answer\n"); ++ ast_hangup(chan); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ goto record_cleanup; ++ } ++ ++ sip_pvt_lock(targetcall_pvt); ++ ++ if (record_data->outgoing) { ++ targetcall_pvt->recordoutpvt = dialog_ref(pvt, "copying pvt into recordoutpvt"); ++ } else { ++ targetcall_pvt->recordinpvt = dialog_ref(pvt, "copying pvt into recordinpvt"); ++ } ++ ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ ++ if (!ast_check_hangup(chan)) { ++ ast_pbx_run(chan); ++ } ++ ++record_cleanup: ++ ast_string_field_free_memory(record_data); ++ ast_free(record_data); ++ ++ return NULL; ++} ++ ++static void start_record_thread(const char *callid, const char *tag, const char *theirtag, int outgoing) ++{ ++ pthread_t threadid; ++ struct record_data *record_data; ++ ++ if (!(record_data = ast_calloc_with_stringfields(1, struct record_data, 128))) { ++ return; ++ } ++ ++ ast_string_field_set(record_data, callid, callid); ++ ast_string_field_set(record_data, tag, tag); ++ ast_string_field_set(record_data, theirtag, theirtag); ++ record_data->outgoing = outgoing; ++ ++ if (ast_pthread_create_detached_background(&threadid, NULL, record_thread, record_data)) { ++ ast_string_field_free_memory(record_data); ++ ast_free(record_data); ++ } ++} ++ ++/*! \brief Handle remotecc start recording requests */ ++static int handle_remotecc_startrecording(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ transmit_response(pvt, "202 Accepted", req); ++ start_record_thread(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag, 1); ++ ++ return 0; ++} ++ ++/*! \brief Handle remotecc stop recording requests */ ++static int handle_remotecc_stoprecording(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_pvt *targetcall_pvt; ++ struct ast_channel *chan; ++ ++ if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { ++ ast_debug(1, "dialogid call leg does not exist\n"); ++ return -1; ++ } ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ if (targetcall_pvt->recordoutpvt) { ++ sip_pvt_lock(targetcall_pvt->recordoutpvt); ++ if ((chan = targetcall_pvt->recordoutpvt->owner)) { ++ ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); ++ } ++ sip_pvt_unlock(targetcall_pvt->recordoutpvt); ++ ++ sip_pvt_lock(targetcall_pvt); ++ targetcall_pvt->recordoutpvt = dialog_unref(targetcall_pvt->recordoutpvt, "drop recordoutpvt"); ++ sip_pvt_unlock(targetcall_pvt); ++ } ++ ++ if (targetcall_pvt->recordinpvt) { ++ sip_pvt_lock(targetcall_pvt->recordinpvt); ++ if ((chan = targetcall_pvt->recordinpvt->owner)) { ++ ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); ++ } ++ sip_pvt_unlock(targetcall_pvt->recordinpvt); ++ ++ sip_pvt_lock(targetcall_pvt); ++ targetcall_pvt->recordinpvt = dialog_unref(targetcall_pvt->recordinpvt, "drop recordinpvt"); ++ sip_pvt_unlock(targetcall_pvt); ++ } ++ ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ ++ return 0; ++} ++ ++static void parse_rtp_stats(struct sip_pvt *pvt, struct sip_request *req) ++{ ++ char *rxstat, *txstat; ++ int dur = 0, rxpkt = 0, rxoct = 0, txpkt = 0, txoct = 0, latepkt = 0, lostpkt = 0, avgjit = 0; ++ struct sip_peer *peer; ++ ++ rxstat = ast_strdupa(sip_get_header(req, "RTP-RxStat")); ++ while (!ast_strlen_zero(rxstat)) { ++ char *tag, *value; ++ ++ tag = strsep(&rxstat, "="); ++ if (!(value = strsep(&rxstat, ","))) { ++ break; ++ } ++ ++ if (!strcasecmp(tag, "Dur")) { ++ dur = atoi(value); ++ } else if (!strcasecmp(tag, "Pkt")) { ++ rxpkt = atoi(value); ++ } else if (!strcasecmp(tag, "Oct")) { ++ rxoct = atoi(value); ++ } else if (!strcasecmp(tag, "LatePkt")) { ++ latepkt = atoi(value); ++ } else if (!strcasecmp(tag, "LostPkt")) { ++ lostpkt = atoi(value); ++ } else if (!strcasecmp(tag, "AvgJit")) { ++ avgjit = atoi(value); ++ } ++ } ++ ++ txstat = ast_strdupa(sip_get_header(req, "RTP-TxStat")); ++ while (!ast_strlen_zero(txstat)) { ++ char *tag, *value; ++ ++ tag = strsep(&txstat, "="); ++ if (!(value = strsep(&txstat, ","))) { ++ break; ++ } ++ ++ if (!strcasecmp(tag, "Pkt")) { ++ txpkt = atoi(value); ++ } else if (!strcasecmp(tag, "Oct")) { ++ txoct = atoi(value); ++ } ++ } ++ ++ ast_verb(3, "Call Quality Report for %s\n" ++ " Duration : %d\n" ++ " Sent Packets : %d\n" ++ " Sent Bytes : %d\n" ++ " Received Packets: %d\n" ++ " Received Bytes : %d\n" ++ " Late Packets : %d\n" ++ " Lost Packets : %d\n" ++ " Average Jitter : %d\n", ++ pvt->peername, dur, txpkt, txoct, rxpkt, rxoct, latepkt, lostpkt, avgjit); ++ ++ if ((peer = sip_find_peer(pvt->peername, NULL, TRUE, FINDALLDEVICES, FALSE, 0))) { ++ send_qrt_url(peer); ++ sip_unref_peer(peer, "unref after sip_find_peer"); ++ } ++} ++ ++static void send_qrt_url(struct sip_peer *peer) ++{ ++ struct sip_pvt *pvt; ++ struct ast_str *content = ast_str_alloca(8192); ++ struct ast_str *url = ast_str_alloca(1024); ++ ++ if (ast_strlen_zero(peer->cisco_qrt_url)) { ++ return; ++ } ++ ++ ast_str_set(&url, 0, "%s", peer->cisco_qrt_url); ++ ast_str_append(&url, 0, "%sname=%s", strchr(ast_str_buffer(url), '?') ? "&" : "?", peer->cisco_devicename); ++ ++ if (!((pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0)))) { ++ return; ++ } ++ ++ set_socket_transport(&pvt->socket, 0); ++ if (create_addr_from_peer(pvt, peer)) { ++ dialog_unlink_all(pvt); ++ dialog_unref(pvt, "create_addr_from_peer failed"); ++ return; ++ } ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "StationSequenceLast\n"); ++ ast_str_append(&content, 0, "2\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n", ast_str_buffer(url)); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++} ++ ++/*! \brief Handle remotecc quality reporting tool requests */ ++static int handle_remotecc_qrt(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_pvt *targetcall_pvt, *refer_pvt; ++ struct ast_str *content = ast_str_alloca(8192); ++ ++ if (!ast_strlen_zero(remotecc_data->dialogid.callid)) { ++ /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ ++ if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { ++ ast_debug(1, "dialogid call leg does not exist\n"); ++ return -1; ++ } ++ ++ ast_set_flag(&targetcall_pvt->flags[2], SIP_PAGE3_RTP_STATS_ON_BYE); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return 0; ++ } ++ copy_pvt_data(refer_pvt, pvt); ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "notify_display\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.callid); ++ ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.localtag); ++ ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.remotetag); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Quality Reporting Tool is active\n"); ++ ast_str_append(&content, 0, "5\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "1\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ transmit_refer_with_content(refer_pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); ++ dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); ++ } else { ++ transmit_response(pvt, "202 Accepted", req); ++ send_qrt_url(peer); ++ } ++ ++ return 0; ++} ++ ++/*! \brief Handle remotecc malicious call requests */ ++static int handle_remotecc_mcid(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_pvt *targetcall_pvt, *refer_pvt; ++ struct ast_channel *chan, *bridged; ++ struct ast_str *content = ast_str_alloca(8192); ++ ++ /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ ++ if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { ++ ast_debug(1, "dialogid call leg does not exist\n"); ++ return -1; ++ } ++ ++ sip_pvt_lock(targetcall_pvt); ++ ++ if (!(chan = targetcall_pvt->owner)) { ++ ast_debug(1, "no owner channel\n"); ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ return -1; ++ } ++ ++ ast_channel_ref(chan); ++ ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ if ((bridged = ast_channel_bridge_peer(chan))) { ++ ast_queue_control(chan, AST_CONTROL_MCID); ++ ast_verb(3, "%s has a malicious call from '%s'\n", targetcall_pvt->peername, ast_channel_name(bridged)); ++ ast_channel_unref(bridged); ++ } ++ ++ ast_channel_unref(chan); ++ ++ if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return 0; ++ } ++ copy_pvt_data(refer_pvt, pvt); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "notify_display\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.callid); ++ ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.localtag); ++ ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.remotetag); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\200T\n"); ++ ast_str_append(&content, 0, "10\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "1\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.callid); ++ ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.localtag); ++ ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.remotetag); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "DtZipZip\n"); ++ ast_str_append(&content, 0, "all\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(refer_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); ++ ++ return 0; ++} ++ ++static int remotecc_callback_notify(const char *context, const char *exten, struct ast_state_cb_info *info, void *data) ++{ ++ struct sip_peer *peer = data; ++ struct sip_pvt *pvt; ++ struct ast_str *content = ast_str_alloca(8192); ++ int is79xx = strstr(peer->useragent, "CP79") ? 1 : 0; ++ struct timeval tv; ++ struct ast_tm tm; ++ char date[32]; ++ ++ if (info->exten_state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY) || info->presence_state == AST_PRESENCE_DND) { ++ peer->callback->busy = 1; ++ return 0; ++ } else if (info->exten_state != AST_EXTENSION_NOT_INUSE || info->presence_state != AST_PRESENCE_AVAILABLE || !peer->callback->busy) { ++ return 0; ++ } ++ ++ tv = ast_tvnow(); ++ ast_strftime(date, sizeof(date), "%X %x", ast_localtime(&tv, &tm, NULL)); ++ ++ if (!((pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0)))) { ++ return 0; ++ } ++ ++ set_socket_transport(&pvt->socket, 0); ++ if (create_addr_from_peer(pvt, peer)) { ++ dialog_unlink_all(pvt); ++ dialog_unref(pvt, "create_addr_from_peer failed"); ++ return 0; ++ } ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "StationSequenceLast\n"); ++ ast_str_append(&content, 0, "2\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ ++ ast_str_reset(content); ++ ++ if (!((pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0)))) { ++ return 0; ++ } ++ ++ set_socket_transport(&pvt->socket, 0); ++ if (create_addr_from_peer(pvt, peer)) { ++ dialog_unlink_all(pvt); ++ dialog_unref(pvt, "create_addr_from_peer failed"); ++ return 0; ++ } ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%d\n", REMOTECC_CALLBACK); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "StationSequenceLast\n"); ++ ast_str_append(&content, 0, "2\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "CallBack\n"); ++ ast_str_append(&content, 0, "%s is now available at %s.\n\nPress Dial to call.\nPress Cancel to deactivate.\nPress Exit to quit this screen.\n", peer->callback->exten, date); ++ ast_str_append(&content, 0, "Please select\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Exit\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 3 : 1); ++ ast_str_append(&content, 0, "SoftKey:Exit\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Cancel\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 2 : 2); ++ ast_str_append(&content, 0, "UserCallData:%d:0:0:0:Cancel\n", REMOTECC_CALLBACK); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Dial\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 1 : 3); ++ ast_str_append(&content, 0, "UserCallData:%d:0:0:0:Dial\n", REMOTECC_CALLBACK); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ ++ return 0; ++} ++ ++/*! \brief Handle remotecc callback requests */ ++static int handle_remotecc_callback(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_pvt *refer_pvt; ++ struct ast_str *content = ast_str_alloca(8192); ++ int is79xx = strstr(sip_get_header(req, "User-Agent"), "CP79") ? 1 : 0; ++ ++ if (!ast_strlen_zero(remotecc_data->dialogid.callid)) { ++ struct sip_pvt *targetcall_pvt; ++ struct ast_channel *chan = NULL; ++ char *exten, *subtype = NULL, *message = NULL; ++ int exten_state, presence_state; ++ int res = -1; ++ ++ /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ ++ if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { ++ ast_debug(1, "dialogid call leg does not exist\n"); ++ return -1; ++ } ++ ++ sip_pvt_lock(targetcall_pvt); ++ ++ if (!(chan = targetcall_pvt->owner)) { ++ ast_debug(1, "no owner channel\n"); ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ return -1; ++ } ++ ++ ast_channel_ref(chan); ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ ++ ast_channel_lock(chan); ++ exten = ast_strdupa(S_COR(ast_channel_connected(chan)->id.number.valid, ast_channel_connected(chan)->id.number.str, targetcall_pvt->exten)); ++ ast_channel_unlock(chan); ++ ast_channel_unref(chan); ++ ++ if (peer->callback) { ++ destroy_callback(peer); ++ peer->callback = NULL; ++ } ++ ++ if (ast_strlen_zero(exten)) { ++ goto callback_cleanup; ++ } ++ if (!(peer->callback = ast_calloc(1, sizeof(*peer->callback)))) { ++ goto callback_cleanup; ++ } ++ if (!(peer->callback->exten = ast_strdup(exten))) { ++ ast_free(peer->callback); ++ peer->callback = NULL; ++ goto callback_cleanup; ++ } ++ sip_ref_peer(peer, "copying peer into callback struct"); ++ if (!(peer->callback->stateid = ast_extension_state_add(peer->context, peer->callback->exten, remotecc_callback_notify, peer))) { ++ sip_unref_peer(peer, "copying peer into callback struct failed"); ++ ast_free(peer->callback->exten); ++ ast_free(peer->callback); ++ goto callback_cleanup; ++ } ++ ++ exten_state = ast_extension_state(NULL, peer->context, peer->callback->exten); ++ presence_state = ast_hint_presence_state(NULL, peer->context, peer->callback->exten, &subtype, &message); ++ ++ ast_free(subtype); ++ ast_free(message); ++ ++ if (exten_state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY) || presence_state == AST_PRESENCE_DND) { ++ peer->callback->busy = 1; ++ } ++ ++ ast_channel_hangupcause_set(chan, AST_CAUSE_FAILURE); ++ ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); ++ res = 0; ++ ++ callback_cleanup: ++ if (res) { ++ transmit_response(pvt, "202 Accepted", req); ++ ++ if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return 0; ++ } ++ copy_pvt_data(refer_pvt, pvt); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "StationSequenceLast\n"); ++ ast_str_append(&content, 0, "2\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "CallBack\n"); ++ ast_str_append(&content, 0, "Unable to activate callback on %s\n", S_OR(exten, CALLERID_UNKNOWN)); ++ ast_str_append(&content, 0, "Please select\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Exit\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 3 : 1); ++ ast_str_append(&content, 0, "SoftKey:Exit\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(refer_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); ++ ++ return 0; ++ } ++ } else if (!ast_strlen_zero(remotecc_data->usercalldata) && peer->callback) { ++ transmit_response(pvt, "202 Accepted", req); ++ ++ if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return 0; ++ } ++ copy_pvt_data(refer_pvt, pvt); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "StationSequenceLast\n"); ++ ast_str_append(&content, 0, "2\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(refer_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); ++ ++ if (!strcmp(remotecc_data->usercalldata, "Dial")) { ++ ast_str_reset(content); ++ ++ if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return 0; ++ } ++ copy_pvt_data(refer_pvt, pvt); ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", peer->callback->exten); ++ ast_str_append(&content, 0, "%d\n", peer->cisco_lineindex); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ transmit_refer_with_content(refer_pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); ++ dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); ++ ++ destroy_callback(peer); ++ peer->callback = NULL; ++ } else if (!strcmp(remotecc_data->usercalldata, "Cancel")) { ++ destroy_callback(peer); ++ peer->callback = NULL; ++ } ++ ++ return 0; ++ } ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return 0; ++ } ++ copy_pvt_data(refer_pvt, pvt); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%d\n", REMOTECC_CALLBACK); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "StationSequenceLast\n"); ++ ast_str_append(&content, 0, "2\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "CallBack\n"); ++ ++ if (peer->callback) { ++ ast_str_append(&content, 0, "CallBack is activated on %s.\n\nPress Cancel to deactivate.\nPress Exit to quit this screen.", peer->callback->exten); ++ } else { ++ ast_str_append(&content, 0, "CallBack is not activated."); ++ } ++ ++ ast_str_append(&content, 0, "Please select\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Exit\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 3 : 1); ++ ast_str_append(&content, 0, "SoftKey:Exit\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ if (peer->callback) { ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Cancel\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 2 : 2); ++ ast_str_append(&content, 0, "UserCallData:%d:0:0:0:Cancel\n", REMOTECC_CALLBACK); ++ ast_str_append(&content, 0, "\n"); ++ } ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(refer_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); ++ ++ return 0; ++} ++ ++/*! \brief Handle remotecc select requests */ ++static int handle_remotecc_select(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_selected *selected; ++ int found = 0; ++ ++ ao2_lock(peer); ++ AST_LIST_TRAVERSE(&peer->selected, selected, entry) { ++ if (!strcmp(remotecc_data->dialogid.callid, selected->callid) && !strcmp(remotecc_data->dialogid.remotetag, selected->tag) && !strcmp(remotecc_data->dialogid.localtag, selected->theirtag)) { ++ found = 1; ++ break; ++ } ++ } ++ ao2_unlock(peer); ++ ++ if (!found) { ++ if (!(selected = ast_calloc_with_stringfields(1, struct sip_selected, 128))) { ++ return -1; ++ } ++ ++ ast_string_field_set(selected, callid, remotecc_data->dialogid.callid); ++ ast_string_field_set(selected, tag, remotecc_data->dialogid.remotetag); ++ ast_string_field_set(selected, theirtag, remotecc_data->dialogid.localtag); ++ ++ ao2_lock(peer); ++ AST_LIST_INSERT_TAIL(&peer->selected, selected, entry); ++ ao2_unlock(peer); ++ } ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ return 0; ++} ++ ++/*! \brief Handle remotecc unselect requests */ ++static int handle_remotecc_unselect(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_selected *selected; ++ ++ ao2_lock(peer); ++ AST_LIST_TRAVERSE_SAFE_BEGIN(&peer->selected, selected, entry) { ++ if (!strcmp(remotecc_data->dialogid.callid, selected->callid) && !strcmp(remotecc_data->dialogid.remotetag, selected->tag) && !strcmp(remotecc_data->dialogid.localtag, selected->theirtag)) { ++ AST_LIST_REMOVE_CURRENT(entry); ++ destroy_selected(selected); ++ break; ++ } ++ } ++ AST_LIST_TRAVERSE_SAFE_END; ++ ao2_unlock(peer); ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ return 0; ++} ++ ++/*! \brief Handle remotecc join requests */ ++static int handle_remotecc_join(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ return handle_remotecc_conference(pvt, req, peer, remotecc_data); ++} ++ ++/*! \brief Handle incoming remotecc request */ ++static int handle_refer_remotecc(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer) ++{ ++#ifdef HAVE_LIBXML2 ++ const char *content_type = sip_get_header(req, "Content-Type"); ++ char *remotecc_body; ++ struct ast_xml_doc *remotecc_doc = NULL; ++ struct ast_xml_node *remotecc_request_node, *remotecc_request_children; ++ struct ast_xml_node *softkeyeventmsg_node, *softkeyeventmsg_children; ++ struct ast_xml_node *datapassthroughreq_node, *datapassthroughreq_children; ++ struct remotecc_data remotecc_data; ++ int start, end, done = 0; ++ char *boundary = NULL; ++ ++ if (!strncasecmp(content_type, "multipart/mixed", 15)) { ++ if ((boundary = strcasestr(content_type, ";boundary="))) { ++ boundary += 10; ++ } else if ((boundary = strcasestr(content_type, "; boundary="))) { ++ boundary += 11; ++ } else { ++ return -1; ++ } ++ boundary = ast_strdupa(boundary); ++ boundary = strsep(&boundary, ";"); ++ ++ if ((start = find_boundary(req, boundary, 0, &done)) == -1) { ++ return -1; ++ } ++ start += 1; ++ if ((end = find_boundary(req, boundary, start, &done)) == -1) { ++ return -1; ++ } ++ ++ content_type = NULL; ++ while (start < end) { ++ const char *line = REQ_OFFSET_TO_STR(req, line[start++]); ++ ++ if (!strncasecmp(line, "Content-Type:", 13)) { ++ content_type = ast_skip_blanks(line + 13); ++ } else if (ast_strlen_zero(line)) { ++ break; ++ } ++ } ++ ++ if (ast_strlen_zero(content_type)) { ++ return -1; ++ } ++ } else { ++ start = 0; ++ end = req->lines; ++ } ++ ++ if (strcasecmp(content_type, "application/x-cisco-remotecc-request+xml")) { ++ ast_log(LOG_WARNING, "Content type is not x-cisco-remotecc-request+xml\n"); ++ return -1; ++ } ++ ++ if (!(remotecc_body = get_content(req, start, end - 1))) { ++ ast_log(LOG_WARNING, "Unable to get remotecc body\n"); ++ return -1; ++ } ++ ++ if (!(remotecc_doc = ast_xml_read_memory(remotecc_body, strlen(remotecc_body)))) { ++ ast_log(LOG_WARNING, "Unable to open XML remotecc document. Is it malformed?\n"); ++ return -1; ++ } ++ ++ if (!(remotecc_request_node = ast_xml_get_root(remotecc_doc))) { ++ ast_log(LOG_WARNING, "Unable to get root node\n"); ++ ast_xml_close(remotecc_doc); ++ return -1; ++ } ++ ++ if (strcasecmp(ast_xml_node_get_name(remotecc_request_node), "x-cisco-remotecc-request")) { ++ ast_log(LOG_WARNING, "Missing x-cisco-remotecc-request node\n"); ++ ast_xml_close(remotecc_doc); ++ return -1; ++ } ++ ++ if (!(remotecc_request_children = ast_xml_node_get_children(remotecc_request_node))) { ++ ast_log(LOG_WARNING, "No tuples in x-cisco-remotecc-request node\n"); ++ ast_xml_close(remotecc_doc); ++ return -1; ++ } ++ ++ memset(&remotecc_data, 0, sizeof(remotecc_data)); ++ ++ if ((softkeyeventmsg_node = ast_xml_find_element(remotecc_request_children, "softkeyeventmsg", NULL, NULL)) && ++ (softkeyeventmsg_children = ast_xml_node_get_children(softkeyeventmsg_node))) { ++ struct ast_xml_node *softkeyevent_node; ++ struct ast_xml_node *dialogid_node, *dialogid_children; ++ struct ast_xml_node *consultdialogid_node, *consultdialogid_children; ++ struct ast_xml_node *joindialogid_node, *joindialogid_children; ++ struct ast_xml_node *callid_node, *localtag_node, *remotetag_node; ++ const char *softkeyevent_text, *callid_text, *localtag_text, *remotetag_text; ++ ++ if ((softkeyevent_node = ast_xml_find_element(softkeyeventmsg_children, "softkeyevent", NULL, NULL))) { ++ softkeyevent_text = ast_xml_get_text(softkeyevent_node); ++ remotecc_data.softkeyevent = ast_strdupa(softkeyevent_text); ++ ast_xml_free_text(softkeyevent_text); ++ } ++ ++ if ((dialogid_node = ast_xml_find_element(softkeyeventmsg_children, "dialogid", NULL, NULL)) && ++ (dialogid_children = ast_xml_node_get_children(dialogid_node))) { ++ if ((callid_node = ast_xml_find_element(dialogid_children, "callid", NULL, NULL))) { ++ callid_text = ast_xml_get_text(callid_node); ++ remotecc_data.dialogid.callid = ast_strdupa(callid_text); ++ ast_xml_free_text(callid_text); ++ } ++ ++ if ((localtag_node = ast_xml_find_element(dialogid_children, "localtag", NULL, NULL))) { ++ localtag_text = ast_xml_get_text(localtag_node); ++ remotecc_data.dialogid.localtag = ast_strdupa(localtag_text); ++ ast_xml_free_text(localtag_text); ++ } ++ ++ if ((remotetag_node = ast_xml_find_element(dialogid_children, "remotetag", NULL, NULL))) { ++ remotetag_text = ast_xml_get_text(remotetag_node); ++ remotecc_data.dialogid.remotetag = ast_strdupa(remotetag_text); ++ ast_xml_free_text(remotetag_text); ++ } ++ } ++ ++ if ((consultdialogid_node = ast_xml_find_element(softkeyeventmsg_children, "consultdialogid", NULL, NULL)) && ++ (consultdialogid_children = ast_xml_node_get_children(consultdialogid_node))) { ++ if ((callid_node = ast_xml_find_element(consultdialogid_children, "callid", NULL, NULL))) { ++ callid_text = ast_xml_get_text(callid_node); ++ remotecc_data.consultdialogid.callid = ast_strdupa(callid_text); ++ ast_xml_free_text(callid_text); ++ } ++ ++ if ((localtag_node = ast_xml_find_element(consultdialogid_children, "localtag", NULL, NULL))) { ++ localtag_text = ast_xml_get_text(localtag_node); ++ remotecc_data.consultdialogid.localtag = ast_strdupa(localtag_text); ++ ast_xml_free_text(localtag_text); ++ } ++ ++ if ((remotetag_node = ast_xml_find_element(consultdialogid_children, "remotetag", NULL, NULL))) { ++ remotetag_text = ast_xml_get_text(remotetag_node); ++ remotecc_data.consultdialogid.remotetag = ast_strdupa(remotetag_text); ++ ast_xml_free_text(remotetag_text); ++ } ++ } ++ ++ if ((joindialogid_node = ast_xml_find_element(softkeyeventmsg_children, "joindialogid", NULL, NULL)) && ++ (joindialogid_children = ast_xml_node_get_children(joindialogid_node))) { ++ if ((callid_node = ast_xml_find_element(joindialogid_children, "callid", NULL, NULL))) { ++ callid_text = ast_xml_get_text(callid_node); ++ remotecc_data.joindialogid.callid = ast_strdupa(callid_text); ++ ast_xml_free_text(callid_text); ++ } ++ ++ if ((localtag_node = ast_xml_find_element(joindialogid_children, "localtag", NULL, NULL))) { ++ localtag_text = ast_xml_get_text(localtag_node); ++ remotecc_data.joindialogid.localtag = ast_strdupa(localtag_text); ++ ast_xml_free_text(localtag_text); ++ } ++ ++ if ((remotetag_node = ast_xml_find_element(joindialogid_children, "remotetag", NULL, NULL))) { ++ remotetag_text = ast_xml_get_text(remotetag_node); ++ remotecc_data.joindialogid.remotetag = ast_strdupa(remotetag_text); ++ ast_xml_free_text(remotetag_text); ++ } ++ } ++ } else if ((datapassthroughreq_node = ast_xml_find_element(remotecc_request_children, "datapassthroughreq", NULL, NULL)) && ++ (datapassthroughreq_children = ast_xml_node_get_children(datapassthroughreq_node))) { ++ struct ast_xml_node *applicationid_node, *confid_node; ++ const char *applicationid_text, *confid_text; ++ ++ if ((applicationid_node = ast_xml_find_element(datapassthroughreq_children, "applicationid", NULL, NULL))) { ++ applicationid_text = ast_xml_get_text(applicationid_node); ++ remotecc_data.applicationid = atoi(S_OR(applicationid_text, "")); ++ ast_xml_free_text(applicationid_text); ++ } ++ ++ if ((confid_node = ast_xml_find_element(datapassthroughreq_children, "confid", NULL, NULL))) { ++ confid_text = ast_xml_get_text(confid_node); ++ remotecc_data.confid = atoi(S_OR(confid_text, "")); ++ ast_xml_free_text(confid_text); ++ } ++ } ++ ++ ast_xml_close(remotecc_doc); ++ ++ if (boundary && !done) { ++ start = end + 1; ++ if ((end = find_boundary(req, boundary, start, &done)) == -1) { ++ ast_log(LOG_WARNING, "Failed to find end boundary\n"); ++ return -1; ++ } ++ ++ content_type = NULL; ++ while (start < end) { ++ const char *line = REQ_OFFSET_TO_STR(req, line[start++]); ++ ++ if (!strncasecmp(line, "Content-Type:", 13)) { ++ content_type = ast_skip_blanks(line + 13); ++ } else if (ast_strlen_zero(line)) { ++ break; ++ } ++ } ++ ++ if (ast_strlen_zero(content_type)) { ++ return -1; ++ } ++ if (!strcasecmp(content_type, "application/x-cisco-remotecc-cm+xml")) { ++ char *usercalldata; ++ ++ if (!(usercalldata = get_content(req, start, end - 1))) { ++ ast_log(LOG_WARNING, "Unable to get usercalldata body\n"); ++ return -1; ++ } ++ ++ remotecc_data.usercalldata = ast_trim_blanks(ast_strdupa(usercalldata)); ++ } ++ } ++ ++ if (!ast_strlen_zero(remotecc_data.softkeyevent)) { ++ if (!strcmp(remotecc_data.softkeyevent, "IDivert")) { ++ return handle_remotecc_idivert(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "HLog")) { ++ return handle_remotecc_hlog(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "Conference")) { ++ return handle_remotecc_conference(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "ConfList")) { ++ return handle_remotecc_conflist(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "ConfDetails")) { ++ return handle_remotecc_conflist(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "RmLastConf")) { ++ return handle_remotecc_rmlastconf(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "Cancel")) { ++ transmit_response(pvt, "202 Accepted", req); ++ return 0; ++ } else if (!strcmp(remotecc_data.softkeyevent, "Park")) { ++ return handle_remotecc_park(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "ParkMonitor")) { ++ return handle_remotecc_parkmonitor(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "StartRecording")) { ++ return handle_remotecc_startrecording(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "StopRecording")) { ++ return handle_remotecc_stoprecording(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "QRT")) { ++ return handle_remotecc_qrt(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "MCID")) { ++ return handle_remotecc_mcid(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "CallBack")) { ++ return handle_remotecc_callback(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "Select")) { ++ return handle_remotecc_select(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "Unselect")) { ++ return handle_remotecc_unselect(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "Join")) { ++ return handle_remotecc_join(pvt, req, peer, &remotecc_data); ++ } ++ ++ ast_log(LOG_WARNING, "Unsupported softkeyevent: %s\n", remotecc_data.softkeyevent); ++ } else if (remotecc_data.applicationid) { ++ if (remotecc_data.applicationid == REMOTECC_CONFLIST) { ++ return handle_remotecc_conflist(pvt, req, peer, &remotecc_data); ++ } else if (remotecc_data.applicationid == REMOTECC_CALLBACK) { ++ return handle_remotecc_callback(pvt, req, peer, &remotecc_data); ++ } ++ ++ ast_log(LOG_WARNING, "Unsupported applicationid: %d\n", remotecc_data.applicationid); ++ } else { ++ ast_log(LOG_WARNING, "Unsupported x-cisco-remotecc-request+xml request\n"); ++ } ++#endif ++ return -1; ++} ++ + /*! \brief Get tag from packet + * + * \return pointer to the provided tag buffer. +@@ -25738,16 +29421,133 @@ + return 0; + } + ++/*! \brief Handle dialog notifications */ ++static int handle_notify_dialog(struct sip_pvt *pvt, struct sip_request *req) ++{ ++#ifdef HAVE_LIBXML2 ++ struct sip_peer *peer; ++ const char *content_type = sip_get_header(req, "Content-Type"); ++ char *dialog_body, *name, *domain, *entity, *state; ++ struct ast_xml_doc *dialog_doc = NULL; ++ struct ast_xml_node *dialog_info_node, *dialog_info_children; ++ struct ast_xml_node *dialog_node, *dialog_children; ++ struct ast_xml_node *state_node; ++ const char *entity_attr, *state_text; ++ int offhook = 0; ++ ++ if (strcasecmp(content_type, "application/dialog-info+xml")) { ++ ast_log(LOG_WARNING, "Content type is not application/dialog-info+xml\n"); ++ return -1; ++ } ++ ++ if (!(dialog_body = get_content(req, 0, req->lines - 1))) { ++ ast_log(LOG_WARNING, "Unable to get dialog body\n"); ++ return -1; ++ } ++ ++ if (!(dialog_doc = ast_xml_read_memory(dialog_body, strlen(dialog_body)))) { ++ ast_log(LOG_WARNING, "Unable to open XML dialog document. Is it malformed?\n"); ++ return -1; ++ } ++ ++ if (!(dialog_info_node = ast_xml_get_root(dialog_doc))) { ++ ast_log(LOG_WARNING, "Unable to get root node\n"); ++ ast_xml_close(dialog_doc); ++ return -1; ++ } ++ ++ if (strcasecmp(ast_xml_node_get_name(dialog_info_node), "dialog-info")) { ++ ast_log(LOG_WARNING, "Missing dialog-info node\n"); ++ ast_xml_close(dialog_doc); ++ return -1; ++ } ++ ++ /* We have to use the entity attribute on dialog-info instead of the NOTIFY URI because some ++ * models of Cisco phones only put the first character of the peer name in the From/To/URI */ ++ if (!(entity_attr = ast_xml_get_attribute(dialog_info_node, "entity"))) { ++ ast_log(LOG_WARNING, "Missing entity attribute"); ++ ast_xml_close(dialog_doc); ++ return -1; ++ } ++ ++ entity = ast_strdupa(entity_attr); ++ ast_xml_free_attr(entity_attr); ++ ++ if (!(dialog_info_children = ast_xml_node_get_children(dialog_info_node))) { ++ ast_log(LOG_WARNING, "No tuples in dialog-info node\n"); ++ ast_xml_close(dialog_doc); ++ return -1; ++ } ++ ++ if (!(dialog_node = ast_xml_find_element(dialog_info_children, "dialog", NULL, NULL))) { ++ ast_log(LOG_WARNING, "Missing dialog node\n"); ++ ast_xml_close(dialog_doc); ++ return -1; ++ } ++ ++ if (!(dialog_children = ast_xml_node_get_children(dialog_node))) { ++ ast_log(LOG_WARNING, "No tuples in dialog node\n"); ++ ast_xml_close(dialog_doc); ++ return -1; ++ } ++ ++ if (!(state_node = ast_xml_find_element(dialog_children, "state", NULL, NULL))) { ++ ast_log(LOG_WARNING, "Missing state node\n"); ++ ast_xml_close(dialog_doc); ++ return -1; ++ } ++ ++ state_text = ast_xml_get_text(state_node); ++ state = ast_strdupa(state_text); ++ ast_xml_free_text(state_text); ++ ast_xml_close(dialog_doc); ++ ++ if (!strcasecmp(state, "trying")) { ++ offhook = 1; ++ } else if (!strcasecmp(state, "terminated")) { ++ offhook = -1; ++ } else { ++ ast_log(LOG_WARNING, "Invalid content in state node %s\n", state_text); ++ return -1; ++ } ++ ++ if (parse_uri_legacy_check(entity, "sip:,sips", &name, NULL, &domain, NULL)) { ++ return -1; ++ } ++ SIP_PEDANTIC_DECODE(name); ++ ++ if (!(peer = sip_find_peer(name, NULL, TRUE, FINDPEERS, TRUE, 0))) { ++ ast_log(LOG_WARNING, "Unknown peer '%s'\n", name); ++ return -1; ++ } ++ ++ if (peer->socket.type == pvt->socket.type && !ast_sockaddr_cmp(&peer->addr, &pvt->recv)) { ++ ao2_lock(peer); ++ if ((peer->offhook += offhook) < 0) { ++ peer->offhook = 0; ++ } ++ ao2_unlock(peer); ++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); ++ } ++ ++ sip_unref_peer(peer, "handle_notify_dialog: unref peer from sip_find_peer lookup"); ++ transmit_response(pvt, "200 OK", req); ++ return 0; ++#else ++ return -1; ++#endif ++} ++ + /*! \brief Handle incoming notifications */ + static int handle_request_notify(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, const char *e) + { + /* This is mostly a skeleton for future improvements */ + /* Mostly created to return proper answers on notifications on outbound REFER's */ + int res = 0; +- const char *event = sip_get_header(req, "Event"); ++ char *event = ast_strdupa(sip_get_header(req, "Event")); + char *sep; + +- if( (sep = strchr(event, ';')) ) { /* XXX bug here - overwriting string ? */ ++ if ((sep = strchr(event, ';'))) { + *sep++ = '\0'; + } + +@@ -25762,6 +29562,7 @@ + char *buf, *cmd, *code; + int respcode; + int success = TRUE; ++ const char *type = find_content_type(req); + + /* EventID for each transfer... EventID is basically the REFER cseq + +@@ -25770,7 +29571,13 @@ + Check if we have an owner of this event */ + + /* Check the content type */ +- if (strncasecmp(sip_get_header(req, "Content-Type"), "message/sipfrag", strlen("message/sipfrag"))) { ++ if (strncasecmp(type, "message/sipfrag", strlen("message/sipfrag"))) { ++ if (!strcasecmp(type, "application/x-cisco-remotecc-response+xml")) { ++ transmit_response(p, "200 OK", req); ++ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); ++ return 0; ++ } ++ + /* We need a sipfrag */ + transmit_response(p, "400 Bad request", req); + sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); +@@ -25778,7 +29585,7 @@ + } + + /* Get the text of the attachment */ +- if (ast_strlen_zero(buf = get_content(req))) { ++ if (ast_strlen_zero(buf = get_content(req, 0, req->lines))) { + ast_log(LOG_WARNING, "Unable to retrieve attachment from NOTIFY %s\n", p->callid); + transmit_response(p, "400 Bad request", req); + sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); +@@ -25892,6 +29699,11 @@ + transmit_response(p, "200 OK", req); + } else if (!strcmp(event, "call-completion")) { + res = handle_cc_notify(p, req); ++ } else if (!strcmp(event, "dialog")) { ++ if (handle_notify_dialog(p, req)) { ++ transmit_response(p, "489 Bad event", req); ++ res = -1; ++ } + } else { + /* We don't understand this event. */ + transmit_response(p, "489 Bad event", req); +@@ -27196,6 +31008,11 @@ + + ast_set_flag(&transferer->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Delay hangup */ + ++ /* Cisco phones need a different response code */ ++ if (ast_test_flag(&targetcall_pvt->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ ast_set_flag(&targetcall_pvt->flags[2], SIP_PAGE3_TRANSFER_RESPONSE); ++ } ++ + sip_pvt_unlock(transferer); + ast_channel_unlock(transferer_chan); + *nounlock = 1; +@@ -27339,7 +31156,7 @@ + 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, uint32_t seqno, int *nounlock) ++static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, const char *e, int *nounlock) + { + char *refer_to = NULL; + char *refer_to_context = NULL; +@@ -27348,6 +31165,46 @@ + enum ast_transfer_result transfer_res; + RAII_VAR(struct ast_channel *, transferer, NULL, ast_channel_cleanup); + RAII_VAR(struct ast_str *, replaces_str, NULL, ast_free_ptr); ++ const char *type = find_content_type(req); ++ ++ /* Cisco USECALLMANAGER remotecc and failover */ ++ if (!strcasecmp(type, "application/x-cisco-alarm+xml") || !strcasecmp(type, "application/x-cisco-remotecc-response+xml")) { ++ transmit_response(p, "202 Accepted", req); ++ if (!p->owner) { ++ sip_alreadygone(p); ++ pvt_set_needdestroy(p, "alarm/remotecc device notificaton"); ++ } ++ return 0; ++ } else if (!strcasecmp(type, "application/x-cisco-remotecc-request+xml")) { ++ struct sip_peer *authpeer = NULL; ++ ++ res = check_user_full(p, req, SIP_REFER, e, 0, addr, &authpeer); ++ ++ /* if an authentication response was sent, we are done here */ ++ if (res == AUTH_CHALLENGE_SENT) { ++ return 0; ++ } ++ ++ if (res != AUTH_SUCCESSFUL) { ++ ast_log(LOG_NOTICE, "Failed to authenticate device %s for REFER\n", sip_get_header(req, "From")); ++ transmit_response_reliable(p, "403 Forbidden", req); ++ } else if (handle_refer_remotecc(p, req, authpeer)) { ++ transmit_response(p, "603 Declined (Remotecc failed)", req); ++ if (authpeer) { ++ sip_unref_peer(authpeer, "sip_unref_peer, from handle_request_refer"); ++ } ++ } ++ if (!p->owner) { ++ sip_alreadygone(p); ++ pvt_set_needdestroy(p, "remotecc request"); ++ } ++ return 0; ++ } else if (!strcasecmp(sip_get_header(req, "Refer-To"), "")) { ++ transmit_response(p, "202 Accepted", req); ++ sip_alreadygone(p); ++ pvt_set_needdestroy(p, "token registration"); ++ return 0; ++ } + + if (req->debug) { + ast_verbose("Call %s got a SIP call transfer from %s: (REFER)!\n", +@@ -27610,7 +31467,12 @@ + break; + } + } +- transmit_response_reliable(p, "487 Request Terminated", &p->initreq); ++ /* Cisco phones fail to include the To tag in the ACK response */ ++ if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ transmit_response(p, "487 Request Terminated", &p->initreq); ++ } else { ++ transmit_response_reliable(p, "487 Request Terminated", &p->initreq); ++ } + transmit_response(p, "200 OK", req); + return 1; + } else { +@@ -27827,6 +31689,10 @@ + transmit_response(p, "200 OK", req); + } + ++ if (ast_test_flag(&p->flags[2], SIP_PAGE3_RTP_STATS_ON_BYE)) { ++ parse_rtp_stats(p, req); ++ } ++ + /* Destroy any pending invites so we won't try to do another + * scheduled reINVITE. */ + stop_reinvite_retry(p); +@@ -28197,7 +32063,7 @@ + return FALSE; + } + +- if (!(pidf_body = get_content(req))) { ++ if (!(pidf_body = get_content(req, 0, req->lines))) { + ast_log(LOG_WARNING, "Unable to get PIDF body\n"); + return FALSE; + } +@@ -28317,6 +32183,102 @@ + return res; + } + ++static int presence_esc_publish_handler(struct sip_pvt *pvt, struct sip_request *req, struct event_state_compositor *esc, struct sip_esc_entry *esc_entry) ++{ ++ struct ast_xml_doc *pidf_doc = NULL; ++ struct ast_xml_node *presence_node; ++ struct ast_xml_node *presence_children; ++ struct ast_xml_node *person_node; ++ struct ast_xml_node *person_children; ++ struct ast_xml_node *activities_node; ++ struct ast_xml_node *activities_children; ++ struct ast_xml_node *dnd_node; ++ struct ast_xml_node *available_node; ++ struct sip_peer *peer; ++ struct sip_alias *alias; ++ int res = 0; ++ int donotdisturb = 0; ++ ++ if (sip_pidf_validate(req, &pidf_doc) == FALSE) { ++ res = -1; ++ } else if (ast_test_flag(&pvt->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ 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 presence_publish_cleanup; ++ } ++ ++ if (!(person_node = ast_xml_find_element(presence_children, "person", NULL, NULL))) { ++ ast_log(LOG_NOTICE, "Couldn't find person node?\n"); ++ res = -1; ++ goto presence_publish_cleanup; ++ } ++ ++ if (!(person_children = ast_xml_node_get_children(person_node))) { ++ ast_log(LOG_NOTICE, "No tuples within person node.\n"); ++ res = -1; ++ goto presence_publish_cleanup; ++ } ++ ++ if (!(activities_node = ast_xml_find_element(person_children, "activities", NULL, NULL))) { ++ ast_log(LOG_NOTICE, "Couldn't find activities node?\n"); ++ res = -1; ++ goto presence_publish_cleanup; ++ } ++ ++ if (!(activities_children = ast_xml_node_get_children(activities_node))) { ++ ast_log(LOG_NOTICE, "No tuples within activities node.\n"); ++ res = -1; ++ goto presence_publish_cleanup; ++ } ++ ++ if ((dnd_node = ast_xml_find_element(activities_children, "dnd", NULL, NULL))) { ++ donotdisturb = 1; ++ } else if ((available_node = ast_xml_find_element(activities_children, "available", NULL, NULL))) { ++ donotdisturb = 0; ++ } else { ++ ast_log(LOG_NOTICE, "Couldn't find dnd or available node?\n"); ++ res = -1; ++ goto presence_publish_cleanup; ++ } ++ ++ if (!(peer = sip_find_peer(pvt->peername, NULL, TRUE, FINDALLDEVICES, FALSE, 0))) { ++ ast_log(LOG_NOTICE, "No such peer '%s'\n", pvt->peername); ++ res = -1; ++ goto presence_publish_cleanup; ++ } ++ ++ if (peer->donotdisturb != donotdisturb) { ++ peer->donotdisturb = donotdisturb; ++ ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", peer->name); ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (alias->peer) { ++ alias->peer->donotdisturb = peer->donotdisturb; ++ ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", alias->peer->name); ++ } ++ } ++ if (!peer->is_realtime) { ++ ast_db_put("SIP/DoNotDisturb", peer->name, peer->donotdisturb ? "yes" : "no"); ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "donotdisturb", donotdisturb ? "yes" : "no", SENTINEL); ++ } ++ } ++ ++ sip_unref_peer(peer, "presence_esc_publish_handler: from sip_find_peer call, setting DoNotDisturb/Available"); ++ } else { ++ res = -1; ++ } ++ ++presence_publish_cleanup: ++ if (pidf_doc) { ++ ast_xml_close(pidf_doc); ++ } ++ if (res) { ++ transmit_response(pvt, "400 Bad Request", req); ++ } ++ return res; ++} + #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) +@@ -28589,6 +32551,182 @@ + return 0; + } + ++enum { ++ FE_BULKUPDATE, ++ FE_DONOTDISTURB, ++ FE_CALLFORWARD ++}; ++ ++/*! \brief Handle incoming feature event SUBSCRIBE body */ ++static int handle_subscribe_featureevent(struct sip_peer *peer, struct sip_request *req, int *feature) ++{ ++#ifdef HAVE_LIBXML2 ++ const char *content_type = sip_get_header(req, "Content-Type"); ++ char *featureevent_body; ++ struct ast_xml_doc *featureevent_doc = NULL; ++ struct ast_xml_node *root_node; ++ ++ if (!atoi(sip_get_header(req, "Content-Length"))) { ++ /* Peer is subscribing to the current DoNotDisturb and CallForward state */ ++ *feature = FE_BULKUPDATE; ++ return 0; ++ } ++ ++ if (strcasecmp(content_type, "application/x-as-feature-event+xml")) { ++ ast_log(LOG_WARNING, "Content type is not x-as-feature-event+xml\n"); ++ return -1; ++ } ++ ++ if (!(featureevent_body = get_content(req, 0, req->lines - 1))) { ++ ast_log(LOG_WARNING, "Unable to get feature event body\n"); ++ return -1; ++ } ++ ++ if (!(featureevent_doc = ast_xml_read_memory(featureevent_body, strlen(featureevent_body)))) { ++ ast_log(LOG_WARNING, "Unable to open XML as-feature-event document. Is it malformed?\n"); ++ return -1; ++ } ++ ++ if (!(root_node = ast_xml_get_root(featureevent_doc))) { ++ ast_log(LOG_WARNING, "Unable to get root node\n"); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ if (!strcmp(ast_xml_node_get_name(root_node), "SetDoNotDisturb")) { ++ int donotdisturb; ++ struct ast_xml_node *set_donotdisturb_node, *set_donotdisturb_children; ++ struct ast_xml_node *donotdisturb_on_node; ++ const char *donotdisturb_on_text; ++ ++ set_donotdisturb_node = root_node; ++ ++ if (!(set_donotdisturb_children = ast_xml_node_get_children(set_donotdisturb_node))) { ++ ast_log(LOG_WARNING, "No tuples within SetDoNotDisturb node"); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ if (!(donotdisturb_on_node = ast_xml_find_element(set_donotdisturb_children, "doNotDisturbOn", NULL, NULL))) { ++ ast_log(LOG_WARNING, "Couldn't find doNotDisturbOn node"); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ donotdisturb_on_text = ast_xml_get_text(donotdisturb_on_node); ++ ++ if (!strcmp(donotdisturb_on_text, "true")) { ++ donotdisturb = 1; ++ } else if (!strcmp(donotdisturb_on_text, "false")) { ++ donotdisturb = 0; ++ } else { ++ ast_log(LOG_WARNING, "Invalid content in doNotDisturbOn node %s\n", donotdisturb_on_text); ++ ast_xml_free_text(donotdisturb_on_text); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ ast_xml_free_text(donotdisturb_on_text); ++ ++ if (peer->donotdisturb != donotdisturb) { ++ peer->donotdisturb = donotdisturb; ++ ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", peer->name); ++ if (!peer->is_realtime) { ++ ast_db_put("SIP/DoNotDisturb", peer->name, peer->donotdisturb ? "yes" : "no"); ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "donotdisturb", donotdisturb ? "yes" : "no", SENTINEL); ++ } ++ } ++ ++ *feature = FE_DONOTDISTURB; ++ } else if (!strcmp(ast_xml_node_get_name(root_node), "SetForwarding")) { ++ char callforward[AST_MAX_EXTENSION]; ++ struct ast_xml_node *set_forwarding_node, *set_forwarding_children; ++ struct ast_xml_node *forwarding_type_node, *activate_forward_node, *forward_dn_node; ++ const char *forwarding_type_text, *activate_forward_text, *forward_dn_text; ++ ++ set_forwarding_node = root_node; ++ ++ if (!(set_forwarding_children = ast_xml_node_get_children(set_forwarding_node))) { ++ ast_log(LOG_WARNING, "No tuples within SetForwarding node"); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ if (!(forwarding_type_node = ast_xml_find_element(set_forwarding_children, "forwardingType", NULL, NULL))) { ++ ast_log(LOG_WARNING, "Couldn't find forwardingType node\n"); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ forwarding_type_text = ast_xml_get_text(forwarding_type_node); ++ ++ if (strcmp(forwarding_type_text, "forwardImmediate")) { ++ ast_log(LOG_WARNING, "forwardingType not supported: %s\n", forwarding_type_text); ++ ast_xml_free_text(forwarding_type_text); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ ast_xml_free_text(forwarding_type_text); ++ ++ if (!(activate_forward_node = ast_xml_find_element(set_forwarding_children, "activateForward", NULL, NULL))) { ++ ast_log(LOG_WARNING, "Couldn't find activateForward node"); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ activate_forward_text = ast_xml_get_text(activate_forward_node); ++ ++ if (!strcmp(activate_forward_text, "true")) { ++ if (!(forward_dn_node = ast_xml_find_element(set_forwarding_children, "forwardDN", NULL, NULL))) { ++ ast_log(LOG_WARNING, "Couldn't find forwardDN node\n"); ++ ast_xml_free_text(activate_forward_text); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ forward_dn_text = ast_xml_get_text(forward_dn_node); ++ ast_copy_string(callforward, S_OR(forward_dn_text, ""), sizeof(callforward)); ++ ast_xml_free_text(forward_dn_text); ++ } else if (!strcmp(activate_forward_text, "false")) { ++ callforward[0] = '\0'; ++ } else { ++ ast_log(LOG_WARNING, "Invalid content in activateForward node: %s\n", activate_forward_text); ++ ast_xml_free_text(activate_forward_text); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ ast_xml_free_text(activate_forward_text); ++ ++ if (strcmp(peer->callforward, callforward)) { ++ ast_string_field_set(peer, callforward, callforward); ++ if (!peer->is_realtime) { ++ if (ast_strlen_zero(peer->callforward)) { ++ ast_db_del("SIP/CallForward", peer->name); ++ } else { ++ ast_db_put("SIP/CallForward", peer->name, peer->callforward); ++ } ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "callforward", peer->callforward, SENTINEL); ++ } ++ } ++ ++ *feature = FE_CALLFORWARD; ++ } else { ++ ast_log(LOG_WARNING, "Couldn't find SetDoNotDisturb or SetForwarding node: %s\n", ast_xml_node_get_name(root_node)); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ ast_xml_close(featureevent_doc); ++ return 0; ++#else ++ return -1 ++#endif ++} ++ + /*! \brief Handle incoming SUBSCRIBE request */ + static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, const char *e) + { +@@ -28597,6 +32735,7 @@ + char *event = ast_strdupa(sip_get_header(req, "Event")); /* Get Event package name */ + int resubscribe = (p->subscribed != NONE) && !req->ignore; + char *options; ++ int feature = -1; + + if (p->initreq.headers) { + /* We already have a dialog */ +@@ -28730,12 +32869,19 @@ + subscribed = XPIDF_XML; /* Older versions of Polycom firmware will claim pidf+xml, but really they only support xpidf+xml */ + } else { + subscribed = PIDF_XML; /* RFC 3863 format */ ++ if (strstr(p->useragent, "Digium")) { ++ ast_set_flag(&p->flags[2], SIP_PAGE3_WHY_DIDNT_DIGIUM_THINK_OF_THIS); ++ } + } + } else if (strstr(accept, "application/dialog-info+xml")) { + subscribed = DIALOG_INFO_XML; + /* IETF draft: draft-ietf-sipping-dialog-package-05.txt */ + } else if (strstr(accept, "application/cpim-pidf+xml")) { +- subscribed = CPIM_PIDF_XML; /* RFC 3863 format */ ++ if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ subscribed = PIDF_XML; /* RFC 3863 format + Cisco USECALLMANAGER */ ++ } else { ++ subscribed = CPIM_PIDF_XML; /* RFC 3863 format */ ++ } + } else if (strstr(accept, "application/xpidf+xml")) { + subscribed = XPIDF_XML; /* Early pre-RFC 3863 format with MSN additions (Microsoft Messenger) */ + } else { +@@ -28858,6 +33004,33 @@ + p->relatedpeer = sip_ref_peer(authpeer, "setting dialog's relatedpeer pointer"); + } + /* Do not release authpeer here */ ++ } else if (!strcmp(event, "as-feature-event")) { ++ if (!authpeer || handle_subscribe_featureevent(authpeer, req, &feature) == -1) { ++ transmit_response(p, "489 Bad Event", req); ++ pvt_set_needdestroy(p, "unknown format"); ++ if (authpeer) { ++ sip_unref_peer(authpeer, "sip_unref_peer, from handle_request_subscribe (authpeer 4)"); ++ } ++ return 0; ++ } ++ ++ p->subscribed = FEATURE_EVENTS; ++ if (authpeer->fepvt != p) { /* Destroy old PVT if this is a new one */ ++ /* We only allow one subscription per peer */ ++ if (authpeer->fepvt) { ++ dialog_unlink_all(authpeer->fepvt); ++ authpeer->fepvt = dialog_unref(authpeer->fepvt, "unref dialog authpeer->fepvt"); ++ } ++ authpeer->fepvt = dialog_ref(p, "setting peers' fepvt to p"); ++ } ++ ++ if (p->relatedpeer != authpeer) { ++ if (p->relatedpeer) { ++ sip_unref_peer(p->relatedpeer, "Unref previously stored relatedpeer ptr"); ++ } ++ p->relatedpeer = sip_ref_peer(authpeer, "setting dialog's relatedpeer pointer"); ++ } ++ /* 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 */ +@@ -28902,6 +33075,9 @@ + if (p->subscribed == MWI_NOTIFICATION && p->relatedpeer) { + ast_debug(2, "%s subscription for mailbox notification - peer %s\n", + action, p->relatedpeer->name); ++ } else if (p->subscribed == FEATURE_EVENTS) { ++ ast_debug(2, "%s feature event subscription for peer %s\n", ++ action, p->username); + } else if (p->subscribed == CALL_COMPLETION) { + ast_debug(2, "%s CC subscription for peer %s\n", action, p->username); + } else { +@@ -28924,10 +33100,38 @@ + struct sip_peer *peer = p->relatedpeer; + sip_ref_peer(peer, "ensure a peer ref is held during MWI sending"); + ao2_unlock(p); +- sip_send_mwi_to_peer(peer, 0); ++ sip_send_mwi(peer, 0); + ao2_lock(p); + sip_unref_peer(peer, "release a peer ref now that MWI is sent"); + } ++ } else if (p->subscribed == FEATURE_EVENTS) { ++ struct sip_request resp; ++ ++ ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); ++ respprep(&resp, p, "200 OK", req); ++ if (feature != FE_BULKUPDATE) { ++ add_header(&resp, "Content-Type", "application/x-as-feature-event+xml"); ++ add_content(&resp, "\n"); ++ if (feature == FE_DONOTDISTURB) { ++ add_content(&resp, "\n"); ++ } else if (feature == FE_CALLFORWARD) { ++ add_content(&resp, "\n"); ++ } ++ } ++ send_response(p, &resp, XMIT_UNRELIABLE, 0); ++ ++ if (p->relatedpeer) { ++ struct sip_peer *peer = p->relatedpeer; ++ sip_ref_peer(peer, "ensure a peer ref is held during feature events sending"); ++ if (feature == FE_BULKUPDATE) { ++ sip_send_bulkupdate(peer); ++ } else if (feature == FE_DONOTDISTURB) { ++ sip_send_donotdisturb(peer); ++ } else if (feature == FE_CALLFORWARD) { ++ sip_send_callforward(peer); ++ } ++ sip_unref_peer(peer, "release a peer ref now that feature events is sent"); ++ } + } else if (p->subscribed != CALL_COMPLETION) { + struct state_notify_data data = { 0, }; + char *subtype = NULL; +@@ -28948,6 +33152,9 @@ + + sip_pvt_unlock(p); + data.state = ast_extension_state_extended(NULL, p->context, p->exten, &device_state_info); ++ data.presence_state = ast_hint_presence_state(NULL, p->context, p->exten, &subtype, &message); ++ data.presence_subtype = subtype; ++ data.presence_message = message; + sip_pvt_lock(p); + + if (data.state < 0) { +@@ -28962,11 +33169,7 @@ + } + return 0; + } +- if (allow_notify_user_presence(p)) { +- data.presence_state = ast_hint_presence_state(NULL, p->context, p->exten, &subtype, &message); +- data.presence_subtype = subtype; +- data.presence_message = message; +- } ++ + ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); + transmit_response(p, "200 OK", req); + /* RFC 3265: A notification must be sent on every subscribe, so force it */ +@@ -29313,7 +33516,7 @@ + + break; + case SIP_REFER: +- res = handle_request_refer(p, req, seqno, nounlock); ++ res = handle_request_refer(p, req, addr, seqno, e, nounlock); + break; + case SIP_CANCEL: + res = handle_request_cancel(p, req); +@@ -29773,7 +33976,7 @@ + * \retval -1 on failure. + * \retval 0 on success. + */ +-static int sip_send_mwi_to_peer(struct sip_peer *peer, int cache_only) ++static int sip_send_mwi(struct sip_peer *peer, int cache_only) + { + /* Called with peer lock, but releases it */ + struct sip_pvt *p; +@@ -29803,7 +34006,7 @@ + if (!get_cached_mwi(peer, &newmsgs, &oldmsgs) && !cache_only) { + /* Fall back to manually checking the mailbox if not cache_only and get_cached_mwi failed */ + struct ast_str *mailbox_str = ast_str_alloca(512); +- peer_mailboxes_to_str(&mailbox_str, peer); ++ get_peer_mailboxes(&mailbox_str, peer); + /* if there is no mailbox do nothing */ + if (!ast_str_strlen(mailbox_str)) { + ao2_unlock(peer); +@@ -29821,7 +34024,7 @@ + + if (peer->mwipvt) { + /* Base message on subscription */ +- p = dialog_ref(peer->mwipvt, "sip_send_mwi_to_peer: Setting dialog ptr p from peer->mwipvt"); ++ p = dialog_ref(peer->mwipvt, "sip_send_mwi: Setting dialog ptr p from peer->mwipvt"); + ao2_unlock(peer); + } else { + ao2_unlock(peer); +@@ -29871,13 +34074,360 @@ + /* the following will decrement the refcount on p as it finishes */ + transmit_notify_with_mwi(p, newmsgs, oldmsgs, vmexten); + sip_pvt_unlock(p); +- dialog_unref(p, "unref dialog ptr p just before it goes out of scope at the end of sip_send_mwi_to_peer."); ++ dialog_unref(p, "unref dialog ptr p just before it goes out of scope at the end of sip_send_mwi."); + + update_peer_lastmsgssent(peer, ((newmsgs > 0x7fff ? 0x7fff0000 : (newmsgs << 16)) | (oldmsgs > 0xffff ? 0xffff : oldmsgs)), 0); + + return 0; + } + ++/*! ++ * \brief Expire bulk-register aliases ++ */ ++static void expire_peer_aliases(struct sip_peer *peer) ++{ ++ struct sip_alias *alias; ++ ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (!alias->peer) { ++ continue; ++ } ++ ++ ast_verb(3, "Unregistered SIP '%s'\n", alias->name); ++ alias->peer->lastms = 0; ++ memset(&alias->peer->addr, 0, sizeof(alias->peer->addr)); ++ ++ if (alias->peer->socket.tcptls_session) { ++ ao2_ref(alias->peer->socket.tcptls_session, -1); ++ alias->peer->socket.tcptls_session = NULL; ++ } else if (alias->peer->socket.ws_session) { ++ ast_websocket_unref(alias->peer->socket.ws_session); ++ alias->peer->socket.ws_session = NULL; ++ } ++ ++ ast_string_field_set(alias->peer, fullcontact, ""); ++ ast_string_field_set(alias->peer, username, ""); ++ ast_string_field_set(alias->peer, useragent, ""); ++ ++ if (alias->peer->endpoint) { ++ RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); ++ ast_endpoint_set_state(alias->peer->endpoint, AST_ENDPOINT_OFFLINE); ++ blob = ast_json_pack("{s: s, s: s}", ++ "peer_status", "Unregistered", ++ "cause", "Expired"); ++ ast_endpoint_blob_publish(alias->peer->endpoint, ast_endpoint_state_type(), blob); ++ } ++ ++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); ++ register_peer_exten(alias->peer, 0); ++ ++ sip_unref_peer(alias->peer, "unref after sip_find_peer"); ++ alias->peer = NULL; ++ } ++} ++ ++/*! ++ * \brief Update bulk-register aliases ++ */ ++static void register_peer_aliases(struct sip_peer *peer) ++{ ++ struct sip_alias *alias; ++ char *scheme, *hostport; ++ ++ if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { ++ return; ++ } ++ ++ scheme = ast_strdupa(peer->fullcontact); ++ if ((hostport = strchr(scheme, ':')) != NULL) { ++ *hostport++ = '\0'; ++ if ((hostport = strchr(hostport, '@')) != NULL) { ++ *hostport++ = '\0'; ++ } ++ } ++ ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (!alias->peer) { ++ if (!(alias->peer = sip_find_peer(alias->name, NULL, TRUE, FINDPEERS, FALSE, 0))) { ++ ast_log(LOG_WARNING, "No such register peer '%s'\n", alias->name); ++ continue; ++ } ++ ++ /* remove any schedules that may have been created */ ++ peer_sched_cleanup(alias->peer); ++ } ++ ++ /* These settings could have been overwritten by a reload */ ++ alias->peer->cisco_lineindex = alias->lineindex; ++ ast_string_field_set(alias->peer, username, alias->name); ++ ++ ast_string_field_set(alias->peer, cisco_authname, peer->name); ++ ast_string_field_set(alias->peer, secret, peer->secret); ++ ast_string_field_set(alias->peer, md5secret, peer->md5secret); ++ ++ ast_string_field_set(alias->peer, regcallid, peer->regcallid); ++ ast_string_field_set(alias->peer, cisco_devicename, peer->cisco_devicename); ++ ast_string_field_set(alias->peer, useragent, peer->useragent); ++ ++ alias->peer->donotdisturb = peer->donotdisturb; ++ alias->peer->huntgroup = peer->huntgroup; ++ ++ /* Peer hasn't changed */ ++ if (!ast_sockaddr_cmp(&peer->addr, &alias->peer->addr)) { ++ continue; ++ } ++ ++ alias->peer->portinuri = peer->portinuri; ++ alias->peer->fromdomainport = peer->fromdomainport; ++ alias->peer->sipoptions = peer->sipoptions; ++ ++ ast_string_field_build(alias->peer, fullcontact, "%s:%s@%s", scheme, alias->peer->name, hostport); ++ ++ alias->peer->addr = peer->addr; ++ copy_socket_data(&alias->peer->socket, &peer->socket); ++ ++ if (alias->peer->endpoint) { ++ RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); ++ ast_endpoint_set_state(alias->peer->endpoint, AST_ENDPOINT_ONLINE); ++ blob = ast_json_pack("{s: s, s: s}", ++ "peer_status", "Registered", ++ "address", ast_sockaddr_stringify(&peer->addr)); ++ ast_endpoint_blob_publish(alias->peer->endpoint, ast_endpoint_state_type(), blob); ++ } ++ ++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); ++ register_peer_exten(alias->peer, 1); ++ ++ ast_verb(3, "Registered SIP '%s' at %s\n", alias->peer->name, ast_sockaddr_stringify(&alias->peer->addr)); ++ alias->peer->offhook = 0; ++ ++ update_peer(alias->peer, max_expiry); ++ update_peer_lastmsgssent(alias->peer, -1, 0); ++ } ++} ++ ++/*! ++ * \brief Register all bulk-register aliases ++ */ ++static void register_all_aliases(void) ++{ ++ struct ao2_iterator i; ++ struct sip_peer *peer; ++ struct sip_alias *alias; ++ ++ if (!speerobjs) { ++ return; ++ } ++ ++ i = ao2_iterator_init(peers, 0); ++ while ((peer = ao2_t_iterator_next(&i, "iterate thru peers table"))) { ++ ao2_lock(peer); ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (alias->peer) { ++ alias->peer->addr = peer->addr; ++ } ++ } ++ register_peer_aliases(peer); ++ ao2_unlock(peer); ++ sip_unref_peer(peer, "toss iterator peer ptr"); ++ } ++ ao2_iterator_destroy(&i); ++} ++ ++/*! ++ * \brief Send donotdisturb, call forward and huntgroup in one bulk update ++ */ ++static int sip_send_bulkupdate(struct sip_peer *peer) ++{ ++ if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { ++ return 0; ++ } ++ ++ if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ struct sip_pvt *pvt; ++ struct ast_str *content; ++ int newmsgs = 0, oldmsgs = 0; ++ struct sip_alias *alias; ++ ++ if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return -1; ++ } ++ ++ if (!(content = ast_str_create(8192))) { ++ dialog_unref(pvt, "drop pvt"); ++ return -1; ++ } ++ ++ /* Don't use create_addr_from_peer here as it may fail due to the peer not having responded to an OPTIONS request yet */ ++ pvt->sa = peer->addr; ++ pvt->recv = peer->addr; ++ copy_socket_data(&pvt->socket, &peer->socket); ++ ast_copy_flags(&pvt->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY); ++ ast_copy_flags(&pvt->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY); ++ ast_copy_flags(&pvt->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY); ++ ++ /* Recalculate our side, and recalculate Call ID */ ++ ast_sip_ouraddrfor(&pvt->sa, &pvt->ourip, pvt); ++ change_callid_pvt(pvt, NULL); ++ ++ if (!ast_strlen_zero(peer->tohost)) { ++ ast_string_field_set(pvt, tohost, peer->tohost); ++ } else { ++ ast_string_field_set(pvt, tohost, ast_sockaddr_stringify_host_remote(&peer->addr)); ++ } ++ if (!pvt->portinuri) { ++ pvt->portinuri = peer->portinuri; ++ } ++ if (peer->fromdomainport) { ++ pvt->fromdomainport = peer->fromdomainport; ++ } ++ ++ ast_string_field_set(pvt, fullcontact, peer->fullcontact); ++ ast_string_field_set(pvt, username, peer->username); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", peer->donotdisturb ? "enable" : "disable"); ++ ast_str_append(&content, 0, "\n", ast_test_flag(&peer->flags[2], SIP_PAGE3_DND_BUSY) ? "callreject" : "ringeroff"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", peer->huntgroup ? "on" : "off"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 9, "\n"); ++ ++ if (!get_cached_mwi(peer, &newmsgs, &oldmsgs)) { ++ struct ast_str *mailbox = ast_str_alloca(512); ++ ++ get_peer_mailboxes(&mailbox, peer); ++ if (ast_str_strlen(mailbox)) { ++ ast_app_inboxcount(ast_str_buffer(mailbox), &newmsgs, &oldmsgs); ++ update_peer_lastmsgssent(peer, ((newmsgs > 0x7fff ? 0x7fff0000 : (newmsgs << 16)) | (oldmsgs > 0xffff ? 0xffff : oldmsgs)), 0); ++ } else { ++ update_peer_lastmsgssent(peer, -1, 0); ++ } ++ } ++ ++ ast_str_append(&content, 0, "\n", peer->cisco_lineindex); ++ ast_str_append(&content, 0, "%s\n", newmsgs ? "yes" : "no"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n", newmsgs, oldmsgs); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", peer->callforward); ++ ast_str_append(&content, 0, "%s\n", !ast_strlen_zero(peer->vmexten) && !strcmp(peer->callforward, peer->vmexten) ? "on" : "off"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ int newmsgs = 0, oldmsgs = 0; ++ ++ if (!alias->peer) { ++ continue; ++ } ++ ++ if (!get_cached_mwi(alias->peer, &newmsgs, &oldmsgs)) { ++ struct ast_str *mailbox = ast_str_alloca(512); ++ ++ get_peer_mailboxes(&mailbox, alias->peer); ++ if (ast_str_strlen(mailbox)) { ++ ast_app_inboxcount(ast_str_buffer(mailbox), &newmsgs, &oldmsgs); ++ update_peer_lastmsgssent(peer, ((newmsgs > 0x7fff ? 0x7fff0000 : (newmsgs << 16)) | (oldmsgs > 0xffff ? 0xffff : oldmsgs)), 0); ++ } else { ++ update_peer_lastmsgssent(peer, -1, 0); ++ } ++ } ++ ++ ast_str_append(&content, 0, "\n", alias->peer->cisco_lineindex); ++ ast_str_append(&content, 0, "%s\n", newmsgs ? "yes" : "no"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n", newmsgs, oldmsgs); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", alias->peer->callforward); ++ ast_str_append(&content, 0, "%s\n", !ast_strlen_zero(alias->peer->vmexten) && !strcmp(alias->peer->callforward, alias->peer->vmexten) ? "on" : "off"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ } ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ ++ ast_free(content); ++ } else if (peer->fepvt) { ++ struct sip_request req; ++ struct sip_pvt *pvt = peer->fepvt; ++ char tmp[512], boundary[32]; ++ ++ ast_set_flag(&pvt->flags[0], SIP_OUTGOING); ++ reqprep(&req, pvt, SIP_NOTIFY, 0, 1); ++ ++ snprintf(boundary, sizeof(boundary), "%08lx%08lx%08lx", ast_random(), ast_random(), ast_random()); ++ ++ add_header(&req, "Event", "as-feature-event"); ++ if (pvt->expiry) { ++ add_header(&req, "Subscription-State", "active"); ++ } else { ++ add_header(&req, "Subscription-State", "terminated;reason=timeout"); ++ } ++ snprintf(tmp, sizeof(tmp), "multipart/mixed; boundary=%s", boundary); ++ add_header(&req, "Content-Type", tmp); ++ ++ snprintf(tmp, sizeof(tmp), "--%s\r\n", boundary); ++ add_content(&req, tmp); ++ add_content(&req, "Content-Type: application/x-as-feature-event+xml\r\n"); ++ add_content(&req, "\r\n"); ++ add_content(&req, "\n"); ++ add_content(&req, "\n"); ++ snprintf(tmp, sizeof(tmp), "\n%s\n", peer->donotdisturb ? "true" : "false"); ++ add_content(&req, tmp); ++ add_content(&req, "\n"); ++ add_content(&req, "\r\n"); ++ ++ snprintf(tmp, sizeof(tmp), "--%s\r\n", boundary); ++ add_content(&req, tmp); ++ add_content(&req, "Content-Type: application/x-as-feature-event+xml\r\n"); ++ add_content(&req, "\r\n"); ++ add_content(&req, "\n"); ++ add_content(&req, "\n"); ++ add_content(&req, "\nforwardImmediate\n"); ++ snprintf(tmp, sizeof(tmp), "%s\n%s\n", !ast_strlen_zero(peer->callforward) ? "true" : "false", peer->callforward); ++ add_content(&req, tmp); ++ add_content(&req, "\n"); ++ add_content(&req, "\r\n"); ++ ++ snprintf(tmp, sizeof(tmp), "--%s--\r\n", boundary); ++ add_content(&req, tmp); ++ send_request(pvt, &req, XMIT_RELIABLE, pvt->ocseq); ++ } ++ ++ return 0; ++} ++ + static struct ast_manager_event_blob *session_timeout_to_ami(struct stasis_message *msg) + { + RAII_VAR(struct ast_str *, channel_string, NULL, ast_free); +@@ -30579,6 +35129,25 @@ + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); + } + ++ if (!AST_LIST_EMPTY(&peer->aliases)) { ++ struct sip_alias *alias; ++ ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (!alias->peer || alias->peer->lastms == -1) { ++ continue; ++ } ++ ++ ast_log(LOG_NOTICE, "Peer '%s' is now UNREACHABLE! Last qualify: %d\n", alias->peer->name, alias->peer->lastms); ++ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Unreachable\r\nTime: %d\r\n", alias->peer->name, -1); ++ ++ alias->peer->lastms = -1; ++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); ++ if (sip_cfg.regextenonqualify) { ++ register_peer_exten(alias->peer, FALSE); ++ } ++ } ++ } ++ + /* Try again quickly */ + AST_SCHED_REPLACE_UNREF(peer->pokeexpire, sched, + DEFAULT_FREQ_NOTOK, sip_poke_peer_s, peer, +@@ -30732,13 +35301,13 @@ + char *host; + char *tmp; + struct sip_peer *p; +- + int res = AST_DEVICE_INVALID; + + /* make sure data is not null. Maybe unnecessary, but better be safe */ + host = ast_strdupa(data ? data : ""); +- if ((tmp = strchr(host, '@'))) ++ if ((tmp = strchr(host, '@'))) { + host = tmp + 1; ++ } + + ast_debug(3, "Checking device state for peer %s\n", host); + +@@ -30776,7 +35345,7 @@ + else if (p->call_limit && p->busy_level && p->inuse >= p->busy_level) + /* We're forcing busy before we've reached the call limit */ + res = AST_DEVICE_BUSY; +- else if (p->call_limit && p->inuse) ++ else if (p->call_limit && (p->inuse || p->offhook)) + /* Not busy, but we do have a call */ + res = AST_DEVICE_INUSE; + else if (p->maxms && ((p->lastms > p->maxms) || (p->lastms < 0))) +@@ -30794,6 +35363,35 @@ + return res; + } + ++static int sip_presencestate(const char *data, char **subtype, char **message) ++{ ++ char *host; ++ char *tmp; ++ struct sip_peer *p; ++ int res = AST_PRESENCE_INVALID; ++ ++ /* make sure data is not null. Maybe unnecessary, but better be safe */ ++ host = ast_strdupa(data ? data : ""); ++ if ((tmp = strchr(host, '@'))) { ++ host = tmp + 1; ++ } ++ ++ ast_debug(3, "Checking presence state for peer %s\n", host); ++ ++ if ((p = sip_find_peer(host, NULL, FALSE, FINDALLDEVICES, TRUE, 0))) { ++ if (!(ast_sockaddr_isnull(&p->addr) && ast_sockaddr_isnull(&p->defaddr))) { ++ if (p->donotdisturb) { ++ res = AST_PRESENCE_DND; ++ } else { ++ res = AST_PRESENCE_AVAILABLE; ++ } ++ } ++ sip_unref_peer(p, "sip_unref_peer, from sip_presencestate, release ref from sip_find_peer"); ++ } ++ ++ return res; ++} ++ + /*! \brief PBX interface function -build SIP pvt structure + * SIP calls initiated by the PBX arrive here. + * +@@ -31288,9 +35886,21 @@ + } else if (!strcasecmp(v->name, "rfc2833compensate")) { + ast_set_flag(&mask[1], SIP_PAGE2_RFC2833_COMPENSATE); + ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_RFC2833_COMPENSATE); +- } else if (!strcasecmp(v->name, "buggymwi")) { +- ast_set_flag(&mask[1], SIP_PAGE2_BUGGY_MWI); +- ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_BUGGY_MWI); ++ } else if (!strcasecmp(v->name, "dndbusy")) { ++ ast_set_flag(&mask[2], SIP_PAGE3_DND_BUSY); ++ ast_set2_flag(&flags[2], ast_true(v->value), SIP_PAGE3_DND_BUSY); ++ } else if (!strcasecmp(v->name, "huntgroup_default")) { ++ ast_set_flag(&mask[2], SIP_PAGE3_HUNTGROUP_DEFAULT); ++ ast_set2_flag(&flags[2], ast_true(v->value), SIP_PAGE3_HUNTGROUP_DEFAULT); ++ } else if (!strcasecmp(v->name, "cisco_usecallmanager")) { ++ ast_set_flag(&mask[1], SIP_PAGE2_CISCO_USECALLMANAGER); ++ ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_CISCO_USECALLMANAGER); ++ } else if (!strcasecmp(v->name, "cisco_keep_conference")) { ++ ast_set_flag(&mask[2], SIP_PAGE3_CISCO_KEEP_CONFERENCE); ++ ast_set2_flag(&flags[2], ast_true(v->value), SIP_PAGE3_CISCO_KEEP_CONFERENCE); ++ } else if (!strcasecmp(v->name, "cisco_multiadmin_conference")) { ++ ast_set_flag(&mask[2], SIP_PAGE3_CISCO_MULTIADMIN_CONFERENCE); ++ ast_set2_flag(&flags[2], ast_true(v->value), SIP_PAGE3_CISCO_MULTIADMIN_CONFERENCE); + } else if (!strcasecmp(v->name, "rtcp_mux")) { + ast_set_flag(&mask[2], SIP_PAGE3_RTCP_MUX); + ast_set2_flag(&flags[2], ast_true(v->value), SIP_PAGE3_RTCP_MUX); +@@ -31552,7 +36162,6 @@ + peer->stimer.st_max_se = global_max_se; + peer->timer_t1 = global_t1; + peer->timer_b = global_timer_b; +- clear_peer_mailboxes(peer); + peer->disallowed_methods = sip_cfg.disallowed_methods; + peer->transports = default_transports; + peer->default_outbound_transport = default_primary_transport; +@@ -31560,6 +36169,8 @@ + ao2_ref(peer->outboundproxy, -1); + peer->outboundproxy = NULL; + } ++ peer->cisco_lineindex = 1; ++ peer->cisco_pickupnotify_timer = 5; + } + + /*! \brief Create temporary peer (used in autocreatepeer mode) */ +@@ -31611,7 +36222,6 @@ + + while ((mbox = strsep(&next, ","))) { + struct sip_mailbox *mailbox; +- int duplicate = 0; + + /* remove leading/trailing whitespace from mailbox string */ + mbox = ast_strip(mbox); +@@ -31622,12 +36232,11 @@ + /* Check whether the mailbox is already in the list */ + AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, entry) { + if (!strcmp(mailbox->id, mbox)) { +- duplicate = 1; +- mailbox->status = SIP_MAILBOX_STATUS_EXISTING; + break; + } + } +- if (duplicate) { ++ if (mailbox) { ++ mailbox->status = SIP_MAILBOX_STATUS_EXISTING; + continue; + } + +@@ -31643,6 +36252,87 @@ + } + } + ++static void add_peer_alias(struct sip_peer *peer, const char *value, int *lineindex) ++{ ++ char *next, *name; ++ struct sip_alias *alias; ++ ++ next = ast_strdupa(value); ++ ++ while ((name = strsep(&next, ","))) { ++ name = ast_strip(name); ++ ++ if (ast_strlen_zero(name)) { ++ continue; ++ } ++ ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (!strcmp(alias->name, name)) { ++ break; ++ } ++ } ++ ++ if (alias) { ++ alias->removed = 0; ++ } else { ++ if (!(alias = ast_calloc(1, sizeof(*alias)))) { ++ return; ++ } ++ ++ if (!(alias->name = ast_strdup(name))) { ++ ast_free(alias); ++ return; ++ } ++ ++ AST_LIST_INSERT_TAIL(&peer->aliases, alias, entry); ++ } ++ ++ alias->lineindex = (*lineindex)++; ++ } ++} ++ ++static void add_peer_subscription(struct sip_peer *peer, const char *value) ++{ ++ char *next, *exten, *context; ++ struct sip_subscription *subscription; ++ ++ next = ast_strdupa(value); ++ ++ while ((exten = strsep(&next, ","))) { ++ if ((context = strchr(exten, '@'))) { ++ *context++ = '\0'; ++ context = ast_strip(context); ++ } else { ++ context = ast_strdupa(S_OR(peer->subscribecontext, peer->context)); ++ } ++ ++ exten = ast_strip(exten); ++ ++ if (ast_strlen_zero(exten) || ast_strlen_zero(context)) { ++ continue; ++ } ++ ++ AST_LIST_TRAVERSE(&peer->subscriptions, subscription, entry) { ++ if (!strcmp(subscription->exten, exten) && !strcmp(subscription->context, context)) { ++ break; ++ } ++ } ++ ++ if (subscription) { ++ subscription->removed = 0; ++ } else { ++ if (!(subscription = ast_calloc_with_stringfields(1, struct sip_subscription, 32))) { ++ return; ++ } ++ ++ ast_string_field_set(subscription, exten, exten); ++ ast_string_field_set(subscription, context, context); ++ ++ AST_LIST_INSERT_TAIL(&peer->subscriptions, subscription, entry); ++ } ++ } ++} ++ + /*! \brief Build peer from configuration (file or realtime static/dynamic) */ + static struct sip_peer *build_peer(const char *name, struct ast_variable *v_head, struct ast_variable *alt, int realtime, int devstate_only) + { +@@ -31666,6 +36356,7 @@ + int alt_fullcontact = alt ? 1 : 0, headercount = 0; + struct ast_str *fullcontact = ast_str_alloca(512); + int acl_change_subscription_needed = 0; ++ int lineindex = 2; + + if (!realtime || ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS)) { + /* Note we do NOT use sip_find_peer here, to avoid realtime recursion */ +@@ -31763,9 +36454,18 @@ + + if (!devstate_only) { + struct sip_mailbox *mailbox; ++ struct sip_alias *alias; ++ struct sip_subscription *subscription; ++ + AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, entry) { + mailbox->status = SIP_MAILBOX_STATUS_UNKNOWN; + } ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ alias->removed = 1; ++ } ++ AST_LIST_TRAVERSE(&peer->subscriptions, subscription, entry) { ++ subscription->removed = 1; ++ } + } + + /* clear named callgroup and named pickup group container */ +@@ -31980,7 +36680,7 @@ + } else if (!strcasecmp(v->name, "regexten")) { + ast_string_field_set(peer, regexten, v->value); + } else if (!strcasecmp(v->name, "callbackextension")) { +- ast_string_field_set(peer, callback, v->value); ++ ast_string_field_set(peer, callbackexten, v->value); + } else if (!strcasecmp(v->name, "amaflags")) { + format = ast_channel_string2amaflag(v->value); + if (format < 0) { +@@ -32150,6 +36850,46 @@ + ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL); + } else if (!strcasecmp(v->name, "force_avp")) { + ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_FORCE_AVP); ++ } else if (!strcasecmp(v->name, "register")) { ++ if (!strcasecmp(peer->name, v->value)) { ++ ast_log(LOG_WARNING, "Invalid register '%s', same name as peer at line %d of %s\n", v->value, v->lineno, config); ++ } else { ++ add_peer_alias(peer, v->value, &lineindex); ++ } ++ } else if (!strcasecmp(v->name, "subscribe")) { ++ add_peer_subscription(peer, v->value); ++ } else if (!strcasecmp(v->name, "cisco_pickupnotify_alert")) { ++ char *val = ast_strdupa(v->value); ++ char *alert; ++ ++ ast_clear_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM | SIP_PAGE3_CISCO_PICKUPNOTIFY_TO | SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP); ++ ++ while ((alert = strsep(&val, ","))) { ++ if (!strcasecmp(alert, "none")) { ++ ast_clear_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM | SIP_PAGE3_CISCO_PICKUPNOTIFY_TO | SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP); ++ } else if (!strcasecmp(alert, "from")) { ++ ast_set_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM); ++ } else if (!strcasecmp(alert, "to")) { ++ ast_set_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_TO); ++ } else if (!strcasecmp(alert, "beep")) { ++ ast_set_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP); ++ } else { ++ ast_log(LOG_WARNING, "Invalid cisco_pickupnotify_alert '%s' at line %d of %s\n", alert, v->lineno, config); ++ } ++ } ++ } else if (!strcasecmp(v->name, "cisco_pickupnotify_timer")) { ++ if (sscanf(v->value, "%030d", &peer->cisco_pickupnotify_timer) != 1 || peer->cisco_pickupnotify_timer < 0 || peer->cisco_pickupnotify_timer > 60) { ++ ast_log(LOG_WARNING, "Invalid cisco_pickupnotify_timer '%s' at line %d of %s\n", v->value, v->lineno, config); ++ peer->cisco_pickupnotify_timer = 5; ++ } ++ } else if (!strcasecmp(v->name, "cisco_qrt_url")) { ++ ast_string_field_set(peer, cisco_qrt_url, v->value); ++ } else if (realtime && !strcasecmp(v->name, "donotdisturb")) { ++ peer->donotdisturb = ast_true(v->value); ++ } else if (realtime && !strcasecmp(v->name, "callforward")) { ++ ast_string_field_set(peer, callforward, v->value); ++ } else if (realtime && !strcasecmp(v->name, "huntgroup")) { ++ peer->huntgroup = ast_true(v->value); + } else { + ast_rtp_dtls_cfg_parse(&peer->dtls_cfg, v->name, v->value); + } +@@ -32232,6 +36972,9 @@ + + if (!devstate_only) { + struct sip_mailbox *mailbox; ++ struct sip_alias *alias; ++ struct sip_subscription *subscription; ++ + AST_LIST_TRAVERSE_SAFE_BEGIN(&peer->mailboxes, mailbox, entry) { + if (mailbox->status == SIP_MAILBOX_STATUS_UNKNOWN) { + AST_LIST_REMOVE_CURRENT(entry); +@@ -32239,6 +36982,20 @@ + } + } + AST_LIST_TRAVERSE_SAFE_END; ++ AST_LIST_TRAVERSE_SAFE_BEGIN(&peer->aliases, alias, entry) { ++ if (alias->removed) { ++ AST_LIST_REMOVE_CURRENT(entry); ++ destroy_alias(alias); ++ } ++ } ++ AST_LIST_TRAVERSE_SAFE_END; ++ AST_LIST_TRAVERSE_SAFE_BEGIN(&peer->subscriptions, subscription, entry) { ++ if (subscription->removed) { ++ AST_LIST_REMOVE_CURRENT(entry); ++ destroy_subscription(subscription); ++ } ++ } ++ AST_LIST_TRAVERSE_SAFE_END; + } + + if (!can_parse_xml && (ast_get_cc_agent_policy(peer->cc_params) == AST_CC_AGENT_NATIVE)) { +@@ -32396,6 +37153,22 @@ + reg_source_db(peer); + } + ++ if (!realtime) { ++ char data[128]; ++ ++ if (!ast_db_get("SIP/DoNotDisturb", peer->name, data, sizeof(data))) { ++ peer->donotdisturb = ast_true(data); ++ } ++ if (!ast_db_get("SIP/CallForward", peer->name, data, sizeof(data))) { ++ ast_string_field_set(peer, callforward, data); ++ } ++ if (!ast_db_get("SIP/HuntGroup", peer->name, data, sizeof(data))) { ++ peer->huntgroup = ast_true(data); ++ } else { ++ peer->huntgroup = ast_test_flag(&peer->flags[2], SIP_PAGE3_HUNTGROUP_DEFAULT) ? 1 : 0; ++ } ++ } ++ + /* If they didn't request that MWI is sent *only* on subscribe, go ahead and + * subscribe to it now. */ + if (!devstate_only && !ast_test_flag(&peer->flags[1], SIP_PAGE2_SUBSCRIBEMWIONLY) && +@@ -32404,7 +37177,12 @@ + /* Send MWI from the event cache only. This is so we can send initial + * MWI if app_voicemail got loaded before chan_sip. If it is the other + * way, then we will get events when app_voicemail gets loaded. */ +- sip_send_mwi_to_peer(peer, 1); ++ sip_send_mwi(peer, 1); ++ } ++ ++ if (!devstate_only && ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && ++ !AST_LIST_EMPTY(&peer->subscriptions)) { ++ extensionstate_subscriptions(peer); + } + + peer->the_mark = 0; +@@ -32412,9 +37190,9 @@ + oldacl = ast_free_acl_list(oldacl); + oldcontactacl = ast_free_acl_list(oldcontactacl); + olddirectmediaacl = ast_free_acl_list(olddirectmediaacl); +- if (!ast_strlen_zero(peer->callback)) { /* build string from peer info */ ++ if (!ast_strlen_zero(peer->callbackexten)) { /* build string from peer info */ + char *reg_string; +- if (ast_asprintf(®_string, "%s?%s:%s:%s@%s/%s", peer->name, S_OR(peer->fromuser, peer->username), S_OR(peer->remotesecret, peer->secret), peer->username, peer->tohost, peer->callback) >= 0) { ++ if (ast_asprintf(®_string, "%s?%s:%s:%s@%s/%s", peer->name, S_OR(peer->fromuser, peer->username), S_OR(peer->remotesecret, peer->secret), peer->username, peer->tohost, peer->callbackexten) >= 0) { + sip_register(reg_string, 0); /* XXX TODO: count in registry_count */ + ast_free(reg_string); + } +@@ -34032,6 +38810,7 @@ + static char *app_dtmfmode = "SIPDtmfMode"; + static char *app_sipaddheader = "SIPAddHeader"; + static char *app_sipremoveheader = "SIPRemoveHeader"; ++static char *app_ciscopage = "SIPCiscoPage"; + #ifdef TEST_FRAMEWORK + static char *app_sipsendcustominfo = "SIPSendCustomINFO"; + #endif +@@ -34163,6 +38942,430 @@ + return 0; + } + ++/*! \brief options for SIPCiscoPage() */ ++enum { ++ APP_CISCOPAGE_MULTICAST = 1 << 0, ++ APP_CISCOPAGE_PORT = 1 << 1, ++ APP_CISCOPAGE_VOLUME = 1 << 2, ++ APP_CISCOPAGE_DISPLAY = 1 << 3, ++ APP_CISCOPAGE_INCLUDEBUSY = 1 << 4, ++ APP_CISCOPAGE_OFFHOOK = 1 << 5, ++ APP_CISCOPAGE_BEEP = 1 << 6 ++}; ++ ++enum { ++ APP_CISCOPAGE_ARG_MULTICAST, ++ APP_CISCOPAGE_ARG_PORT, ++ APP_CISCOPAGE_ARG_VOLUME, ++ APP_CISCOPAGE_ARG_DISPLAY, ++ APP_CISCOPAGE_ARG_ARRAY_SIZE ++}; ++ ++AST_APP_OPTIONS(app_ciscopage_opts, BEGIN_OPTIONS ++ AST_APP_OPTION_ARG('m', APP_CISCOPAGE_MULTICAST, APP_CISCOPAGE_ARG_MULTICAST), ++ AST_APP_OPTION_ARG('p', APP_CISCOPAGE_PORT, APP_CISCOPAGE_ARG_PORT), ++ AST_APP_OPTION_ARG('v', APP_CISCOPAGE_VOLUME, APP_CISCOPAGE_ARG_VOLUME), ++ AST_APP_OPTION_ARG('d', APP_CISCOPAGE_DISPLAY, APP_CISCOPAGE_ARG_DISPLAY), ++ AST_APP_OPTION('b', APP_CISCOPAGE_INCLUDEBUSY), ++ AST_APP_OPTION('o', APP_CISCOPAGE_OFFHOOK), ++ AST_APP_OPTION('a', APP_CISCOPAGE_BEEP) ++END_OPTIONS); ++ ++struct page_target { ++ struct sip_peer *peer; ++ struct ast_rtp_instance *rtp; ++ AST_LIST_ENTRY(page_target) entry; ++}; ++ ++static int sip_ciscopage(struct ast_channel *chan, const char *data) ++{ ++ AST_DECLARE_APP_ARGS(args, ++ AST_APP_ARG(peernames); ++ AST_APP_ARG(options); ++ ); ++ struct ast_flags options = { 0, }; ++ char *option_args[APP_CISCOPAGE_ARG_ARRAY_SIZE]; ++ char *parse, *peername, *codec, display[64]; ++ int volume, port, multicast, includebusy, offhook, beep; ++ struct ast_format *format; ++ AST_LIST_HEAD_NOLOCK(, page_target) targets; ++ struct page_target *target; ++ struct ast_str *content = NULL; ++ struct ast_rtp_instance *mrtp = NULL; ++ struct ast_sockaddr ouraddr; ++ int res = -1; ++ ++ if (ast_strlen_zero(data)) { ++ ast_log(LOG_ERROR, "Cannot call %s without arguments\n", app_ciscopage); ++ return -1; ++ } ++ ++ parse = ast_strdupa(data); ++ AST_STANDARD_APP_ARGS(args, parse); ++ ++ if (ast_strlen_zero(args.peernames)) { ++ ast_log(LOG_ERROR, "No peer names specified\n"); ++ return -1; ++ } ++ ++ if (!ast_strlen_zero(args.options)) { ++ ast_app_parse_options(app_ciscopage_opts, &options, option_args, args.options); ++ } ++ ++ if (ast_test_flag(&options, APP_CISCOPAGE_MULTICAST) && !ast_strlen_zero(option_args[APP_CISCOPAGE_ARG_MULTICAST])) { ++ if (!ast_sockaddr_parse(&ouraddr, option_args[APP_CISCOPAGE_ARG_MULTICAST], PARSE_PORT_FORBID)) { ++ ast_log(LOG_ERROR, "Invalid IP address '%s'\n", option_args[APP_CISCOPAGE_ARG_MULTICAST]); ++ return -1; ++ } ++ ++ if (!ast_sockaddr_is_ipv4_multicast(&ouraddr)) { ++ ast_log(LOG_ERROR, "IP address '%s' is not multicast\n", option_args[APP_CISCOPAGE_ARG_MULTICAST]); ++ return -1; ++ } ++ ++ multicast = 1; ++ } else { ++ multicast = 0; ++ } ++ ++ if (ast_test_flag(&options, APP_CISCOPAGE_PORT) && !ast_strlen_zero(option_args[APP_CISCOPAGE_ARG_PORT])) { ++ port = strtol(option_args[APP_CISCOPAGE_ARG_PORT], NULL, 10); ++ if (port < 20480 || port > 32768 || port % 2) { ++ ast_log(LOG_ERROR, "Invalid port option '%s'\n", option_args[APP_CISCOPAGE_ARG_PORT]); ++ return -1; ++ } ++ } else { ++ port = 20480; ++ } ++ ++ if (ast_test_flag(&options, APP_CISCOPAGE_VOLUME) && !ast_strlen_zero(option_args[APP_CISCOPAGE_ARG_VOLUME])) { ++ volume = strtol(option_args[APP_CISCOPAGE_ARG_VOLUME], NULL, 10); ++ if (volume < 1 || volume > 100) { ++ ast_log(LOG_ERROR, "Invalid volume option '%s'\n", option_args[APP_CISCOPAGE_ARG_VOLUME]); ++ return -1; ++ } ++ } else { ++ volume = -1; ++ } ++ ++ if (ast_test_flag(&options, APP_CISCOPAGE_DISPLAY) && !ast_strlen_zero(option_args[APP_CISCOPAGE_ARG_DISPLAY])) { ++ ast_xml_escape(option_args[APP_CISCOPAGE_ARG_DISPLAY], display, sizeof(display)); ++ } else { ++ display[0] = '\0'; ++ } ++ ++ beep = ast_test_flag(&options, APP_CISCOPAGE_BEEP); ++ includebusy = ast_test_flag(&options, APP_CISCOPAGE_INCLUDEBUSY); ++ offhook = ast_test_flag(&options, APP_CISCOPAGE_OFFHOOK); ++ ++ format = ast_channel_readformat(chan); ++ if (ast_format_cmp(format, ast_format_ulaw) == AST_FORMAT_CMP_EQUAL || ast_format_cmp(format, ast_format_alaw) == AST_FORMAT_CMP_EQUAL) { ++ codec = "G.711"; ++ } else if (ast_format_cmp(format, ast_format_g722) == AST_FORMAT_CMP_EQUAL) { ++ codec = "G.722"; ++ } else if (ast_format_cmp(format, ast_format_g729) == AST_FORMAT_CMP_EQUAL) { ++ codec = "G.729"; ++ } else { ++ ast_log(LOG_ERROR, "Unsupported codec format\n"); ++ return -1; ++ } ++ ++ content = ast_str_create(8192); ++ AST_LIST_HEAD_INIT_NOLOCK(&targets); ++ ++ while ((peername = strsep(&args.peernames, "&"))) { ++ struct sip_peer *peer; ++ struct sip_pvt *pvt; ++ struct ast_rtp_instance *rtp = NULL; ++ struct ast_sockaddr theiraddr; ++ ++ if (!(peer = sip_find_peer(peername, NULL, TRUE, FINDPEERS, FALSE, 0))) { ++ ast_log(LOG_ERROR, "No such peer '%s'\n", peername); ++ continue; ++ } ++ ++ if (!ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ ast_log(LOG_ERROR, "Peer '%s' does not have cisco_usecallmanager enabled\n", peer->name); ++ sip_unref_peer(peer, "unref peer"); ++ continue; ++ } ++ ++ if (ast_sockaddr_isnull(&peer->addr)) { ++ ast_log(LOG_ERROR, "Peer '%s' is not registered\n", peer->name); ++ sip_unref_peer(peer, "unref peer"); ++ continue; ++ } ++ ++ if ((peer->offhook || peer->ringing || peer->inuse || peer->donotdisturb) && !includebusy) { ++ sip_unref_peer(peer, "unref peer"); ++ continue; ++ } ++ ++ if (!multicast) { ++ /* We only need the socket type for ast_sip_ouraddrfor */ ++ struct sip_pvt pvt = { .socket.type = peer->socket.type }; ++ ++ ast_sip_ouraddrfor(&peer->addr, &ouraddr, &pvt); ++ ast_sockaddr_copy(&theiraddr, &peer->addr); ++ ast_sockaddr_set_port(&theiraddr, port); ++ ++ if (!(rtp = ast_rtp_instance_new(peer->engine, sched, &ouraddr, NULL))) { ++ sip_unref_peer(peer, "unref peer"); ++ goto ciscopage_cleanup; ++ } ++ ++ ast_rtp_instance_set_write_format(rtp, ast_channel_readformat(chan)); ++ ast_rtp_instance_set_remote_address(rtp, &theiraddr); ++ ++ ast_rtp_instance_set_qos(rtp, global_tos_audio, global_cos_audio, "SIP RTP"); ++ ast_rtp_instance_activate(rtp); ++ } ++ ++ if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ ast_log(LOG_ERROR, "Unable to build sip pvt data for refer (memory/socket error)\n"); ++ sip_unref_peer(peer, "unref peer"); ++ if (rtp) { ++ ast_rtp_instance_destroy(rtp); ++ } ++ continue; ++ } ++ ++ set_socket_transport(&pvt->socket, 0); ++ if (create_addr_from_peer(pvt, peer)) { ++ dialog_unlink_all(pvt); ++ dialog_unref(pvt, "create_addr_from_peer failed. unref dialog"); ++ sip_unref_peer(peer, "unref peer"); ++ if (rtp) { ++ ast_rtp_instance_destroy(rtp); ++ } ++ continue; ++ } ++ ++ ast_str_reset(content); ++ ++ if (!ast_strlen_zero(display)) { ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "notify_display\n"); ++ ast_str_append(&content, 0, "%s\n", display); ++ ast_str_append(&content, 0, "10\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "1\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ } ++ ++ if (beep) { ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "DtZipZip\n"); ++ ast_str_append(&content, 0, "all\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ } ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "StationSequenceLast\n"); ++ ast_str_append(&content, 0, "2\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ if (volume != -1) { ++ ast_str_append(&content, 0, "\n", volume); ++ } else { ++ ast_str_append(&content, 0, "\n"); ++ } ++ ast_str_append(&content, 0, "audio\n"); ++ ast_str_append(&content, 0, "%s\n", codec); ++ ast_str_append(&content, 0, "receive\n"); ++ ast_str_append(&content, 0, "
%s
\n", ast_sockaddr_stringify_fmt(&ouraddr, AST_SOCKADDR_STR_ADDR)); ++ ast_str_append(&content, 0, "%d\n", port); ++ ast_str_append(&content, 0, "
\n"); ++ ast_str_append(&content, 0, "
\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(pvt, "drop pvt"); ++ ++ if (!(target = ast_calloc(1, sizeof(*target)))) { ++ sip_unref_peer(peer, "unref peer"); ++ if (rtp) { ++ ast_rtp_instance_destroy(rtp); ++ } ++ goto ciscopage_cleanup; ++ } ++ ++ if (offhook) { ++ ao2_lock(peer); ++ peer->offhook += 1; ++ ao2_unlock(peer); ++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); ++ } ++ ++ target->peer = peer; ++ target->rtp = rtp; ++ ++ AST_LIST_INSERT_TAIL(&targets, target, entry); ++ } ++ ++ if (AST_LIST_EMPTY(&targets)) { ++ ast_free(content); ++ return -1; ++ } ++ ++ if (multicast) { ++ ast_sockaddr_set_port(&ouraddr, port); ++ ++ if (!(mrtp = ast_rtp_instance_new("multicast", sched, &bindaddr, "basic"))) { ++ goto ciscopage_cleanup; ++ } ++ ++ ast_rtp_instance_set_write_format(mrtp, ast_channel_readformat(chan)); ++ ast_rtp_instance_set_remote_address(mrtp, &ouraddr); ++ ++ ast_rtp_instance_set_qos(mrtp, global_tos_audio, global_cos_audio, "SIP RTP"); ++ ast_rtp_instance_activate(mrtp); ++ } ++ ++ if (ast_channel_state(chan) != AST_STATE_UP) { ++ if ((res = ast_answer(chan))) { ++ goto ciscopage_cleanup; ++ } ++ } ++ ++ /* Wait 500ms for phones to accept the media stream request */ ++ if ((res = ast_safe_sleep(chan, 500))) { ++ goto ciscopage_cleanup; ++ } ++ ++ for (;;) { ++ struct ast_frame *frame; ++ ++ if (ast_waitfor(chan, 10000) < 1) { ++ break; ++ } ++ frame = ast_read(chan); ++ ++ if (!frame || (frame->frametype == AST_FRAME_CONTROL && frame->subclass.integer == AST_CONTROL_HANGUP)) { ++ if (frame) { ++ ast_frfree(frame); ++ } ++ break; ++ } ++ ++ if (frame->frametype == AST_FRAME_VOICE) { ++ if (mrtp) { ++ ast_rtp_instance_write(mrtp, frame); ++ } else { ++ AST_LIST_TRAVERSE(&targets, target, entry) { ++ ast_rtp_instance_write(target->rtp, frame); ++ } ++ } ++ } ++ ++ ast_frfree(frame); ++ } ++ ++ res = 0; ++ ++ciscopage_cleanup: ++ while ((target = AST_LIST_REMOVE_HEAD(&targets, entry))) { ++ struct sip_pvt *pvt; ++ ++ if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ ast_log(LOG_ERROR, "Unable to build sip pvt data for refer (memory/socket error)\n"); ++ } else { ++ set_socket_transport(&pvt->socket, 0); ++ if (create_addr_from_peer(pvt, target->peer)) { ++ dialog_unlink_all(pvt); ++ dialog_unref(pvt, "create_addr_from_peer failed. unref dialog"); ++ pvt = NULL; ++ } ++ } ++ ++ if (pvt) { ++ ast_str_reset(content); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "StationSequenceLast\n"); ++ ast_str_append(&content, 0, "2\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(pvt, "drop pvt"); ++ } ++ ++ if (offhook) { ++ ao2_lock(target->peer); ++ target->peer->offhook -= 1; ++ ao2_unlock(target->peer); ++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", target->peer->name); ++ } ++ ++ sip_unref_peer(target->peer, "unref peer"); ++ if (target->rtp) { ++ ast_rtp_instance_destroy(target->rtp); ++ } ++ ast_free(target); ++ } ++ ++ if (mrtp) { ++ ast_rtp_instance_destroy(mrtp); ++ } ++ ast_free(content); ++ ++ return res; ++} ++ + #ifdef TEST_FRAMEWORK + /*! \brief Send a custom INFO message via AST_CONTROL_CUSTOM indication */ + static int sip_sendcustominfo(struct ast_channel *chan, const char *data) +@@ -34268,6 +39471,11 @@ + i = ao2_iterator_init(peers, 0); + while ((peer = ao2_t_iterator_next(&i, "iterate thru peers table"))) { + ao2_lock(peer); ++ /* Only poke the primary line */ ++ if (peer->cisco_lineindex > 1) { ++ ao2_unlock(peer); ++ continue; ++ } + /* Don't schedule poking on a peer without qualify */ + if (peer->maxms) { + if (num == global_qualify_peers) { +@@ -34413,6 +39621,9 @@ + /* Send qualify (OPTIONS) to all peers */ + sip_poke_all_peers(); + ++ /* Register aliases now that all peers have been added */ ++ register_all_aliases(); ++ + /* Send keepalive to all peers */ + sip_keepalive_all_peers(); + +@@ -34559,9 +39770,9 @@ + static int peer_ipcmp_cb_full(void *obj, void *arg, void *data, int flags) + { + struct sip_peer *peer = obj, *peer2 = arg; +- char *callback = data; ++ char *callbackexten = data; + +- if (!ast_strlen_zero(callback) && strcasecmp(peer->callback, callback)) { ++ if (!ast_strlen_zero(callbackexten) && strcasecmp(peer->callbackexten, callbackexten)) { + /* We require a callback extension match, but don't have one */ + return 0; + } +@@ -34723,6 +39934,9 @@ + AST_CLI_DEFINE(sip_show_settings, "Show SIP global settings"), + AST_CLI_DEFINE(sip_show_mwi, "Show MWI subscriptions"), + AST_CLI_DEFINE(sip_cli_notify, "Send a notify packet to a SIP peer"), ++ AST_CLI_DEFINE(sip_cli_donotdisturb, "Enables/Disables do not disturb on a SIP peer"), ++ AST_CLI_DEFINE(sip_cli_callforward, "Sets/Removes the call forwarding extension for a SIP peer"), ++ AST_CLI_DEFINE(sip_cli_huntgroup, "Login to/Logout from Hunt Group for a SIP peer"), + AST_CLI_DEFINE(sip_show_channel, "Show detailed SIP channel info"), + AST_CLI_DEFINE(sip_show_history, "Show SIP dialog history"), + AST_CLI_DEFINE(sip_show_peer, "Show details on specific SIP peer"), +@@ -35632,6 +40846,7 @@ + ast_register_application_xml(app_dtmfmode, sip_dtmfmode); + ast_register_application_xml(app_sipaddheader, sip_addheader); + ast_register_application_xml(app_sipremoveheader, sip_removeheader); ++ ast_register_application_xml(app_ciscopage, sip_ciscopage); + #ifdef TEST_FRAMEWORK + ast_register_application_xml(app_sipsendcustominfo, sip_sendcustominfo); + #endif +@@ -35706,6 +40921,7 @@ + + sip_register_tests(); + network_change_stasis_subscribe(); ++ pickup_notify_stasis_subscribe(); + + if (sip_cfg.websocket_enabled) { + ast_websocket_add_protocol("sip", sip_websocket_callback); +@@ -35738,6 +40954,7 @@ + + network_change_stasis_unsubscribe(); + acl_change_event_stasis_unsubscribe(); ++ pickup_notify_stasis_unsubscribe(); + + /* First, take us out of the channel type list */ + ast_channel_unregister(&sip_tech); +diff -durN asterisk-18.22.0.orig/channels/sip/config_parser.c asterisk-18.22.0/channels/sip/config_parser.c +--- asterisk-18.22.0.orig/channels/sip/config_parser.c 2024-04-04 00:47:56.256995050 +1300 ++++ asterisk-18.22.0/channels/sip/config_parser.c 2024-04-04 00:48:19.504358686 +1300 +@@ -255,7 +255,7 @@ + } + + /* copy into sip_registry object */ +- ast_string_field_set(reg, callback, ast_strip_quoted(S_OR(host2.extension, "s"), "\"", "\"")); ++ ast_string_field_set(reg, exten, ast_strip_quoted(S_OR(host2.extension, "s"), "\"", "\"")); + ast_string_field_set(reg, username, ast_strip_quoted(S_OR(user2.user, ""), "\"", "\"")); + ast_string_field_set(reg, hostname, ast_strip_quoted(S_OR(host3.host, ""), "\"", "\"")); + ast_string_field_set(reg, authuser, ast_strip_quoted(S_OR(user3.authuser, ""), "\"", "\"")); +@@ -310,7 +310,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg1, 1) || +- strcmp(reg->callback, "s") || ++ strcmp(reg->exten, "s") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "") || + strcmp(reg->hostname, "domain") || +@@ -339,7 +339,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg2, 1) || +- strcmp(reg->callback, "s") || ++ strcmp(reg->exten, "s") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "") || + strcmp(reg->hostname, "domain") || +@@ -368,7 +368,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg3, 1) || +- strcmp(reg->callback, "s") || ++ strcmp(reg->exten, "s") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "namedomain") || + strcmp(reg->hostname, "domain") || +@@ -397,7 +397,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg4, 1) || +- strcmp(reg->callback, "extension") || ++ strcmp(reg->exten, "extension") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "namedomain") || + strcmp(reg->hostname, "domain") || +@@ -426,7 +426,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg5, 1) || +- strcmp(reg->callback, "extension") || ++ strcmp(reg->exten, "extension") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "namedomain") || + strcmp(reg->hostname, "domain") || +@@ -455,7 +455,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg6, 1) || +- strcmp(reg->callback, "extension") || ++ strcmp(reg->exten, "extension") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "namedomain") || + strcmp(reg->hostname, "domain") || +@@ -484,7 +484,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg7, 1) || +- strcmp(reg->callback, "extension") || ++ strcmp(reg->exten, "extension") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "namedomain") || + strcmp(reg->hostname, "domain") || +@@ -513,7 +513,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg8, 1) || +- strcmp(reg->callback, "extension") || ++ strcmp(reg->exten, "extension") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "namedomain") || + strcmp(reg->hostname, "domain") || +@@ -583,7 +583,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg12, 1) || +- strcmp(reg->callback, "s") || ++ strcmp(reg->exten, "s") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "namedomain") || + strcmp(reg->hostname, "domain") || +@@ -612,7 +612,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg13, 1) || +- strcmp(reg->callback, "s") || ++ strcmp(reg->exten, "s") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "namedomain") || + strcmp(reg->hostname, "domain") || +diff -durN asterisk-18.22.0.orig/channels/sip/include/sip.h asterisk-18.22.0/channels/sip/include/sip.h +--- asterisk-18.22.0.orig/channels/sip/include/sip.h 2024-04-04 00:47:56.256995050 +1300 ++++ asterisk-18.22.0/channels/sip/include/sip.h 2024-04-04 00:48:19.504358686 +1300 +@@ -37,6 +37,7 @@ + #include "asterisk/rtp_engine.h" + #include "asterisk/netsock2.h" + #include "asterisk/features_config.h" ++#include "asterisk/stasis.h" + + #include "route.h" + +@@ -107,7 +108,7 @@ + #define SIP_MIN_PACKET 4096 /*!< Initialize size of memory to allocate for packets */ + #define MAX_HISTORY_ENTRIES 50 /*!< Max entires in the history list for a sip_pvt */ + +-#define INITIAL_CSEQ 101 /*!< Our initial sip sequence number */ ++#define INITIAL_CSEQ 100 /*!< Our initial sip sequence number */ + + #define DEFAULT_MAX_SE 1800 /*!< Session-Timer Default Session-Expires period (RFC 4028) */ + #define DEFAULT_MIN_SE 90 /*!< Session-Timer Default Min-SE period (RFC 4028) */ +@@ -351,7 +352,7 @@ + #define SIP_PAGE2_CALL_ONHOLD_INACTIVE (3 << 19) /*!< D: Inactive hold */ + + #define SIP_PAGE2_RFC2833_COMPENSATE (1 << 21) /*!< DP: Compensate for buggy RFC2833 implementations */ +-#define SIP_PAGE2_BUGGY_MWI (1 << 22) /*!< DP: Buggy CISCO MWI fix */ ++#define SIP_PAGE2_CISCO_USECALLMANAGER (1 << 22) /*!< DP: Enable Cisco USECALLMANAGER phone support */ + #define SIP_PAGE2_DIALOG_ESTABLISHED (1 << 23) /*!< 29: Has a dialog been established? */ + + #define SIP_PAGE2_FAX_DETECT (3 << 24) /*!< DP: Fax Detection support */ +@@ -372,7 +373,7 @@ + #define SIP_PAGE2_FLAGS_TO_COPY \ + (SIP_PAGE2_ALLOWSUBSCRIBE | SIP_PAGE2_ALLOWOVERLAP | SIP_PAGE2_IGNORESDPVERSION | \ + SIP_PAGE2_VIDEOSUPPORT | SIP_PAGE2_T38SUPPORT | SIP_PAGE2_RFC2833_COMPENSATE | \ +- SIP_PAGE2_BUGGY_MWI | SIP_PAGE2_TEXTSUPPORT | SIP_PAGE2_FAX_DETECT | \ ++ SIP_PAGE2_CISCO_USECALLMANAGER | SIP_PAGE2_TEXTSUPPORT | SIP_PAGE2_FAX_DETECT | \ + SIP_PAGE2_UDPTL_DESTINATION | SIP_PAGE2_VIDEOSUPPORT_ALWAYS | SIP_PAGE2_PREFERRED_CODEC | \ + SIP_PAGE2_RPID_IMMEDIATE | SIP_PAGE2_RPID_UPDATE | SIP_PAGE2_SYMMETRICRTP |\ + SIP_PAGE2_Q850_REASON | SIP_PAGE2_HAVEPEERCONTEXT | SIP_PAGE2_USE_SRTP | SIP_PAGE2_TRUST_ID_OUTBOUND) +@@ -389,11 +390,29 @@ + #define SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL (1 << 8) /*!< DGP: Stop telling the peer to start music on hold */ + #define SIP_PAGE3_FORCE_AVP (1 << 9) /*!< DGP: Force 'RTP/AVP' for all streams, even DTLS */ + #define SIP_PAGE3_RTCP_MUX (1 << 10) /*!< DGP: Attempt to negotiate RFC 5761 RTCP multiplexing */ ++#define SIP_PAGE3_WHY_DIDNT_DIGIUM_THINK_OF_THIS (1 << 11) /*!< Duh */ ++#define SIP_PAGE3_DND_BUSY (1 << 12) /*!< DPG: Treat endpoint as busy when DND is enabled */ ++#define SIP_PAGE3_SUBSCRIPTIONSTATE_ACTIVE (1 << 13) /*!< D: Force Subscription-State to be active for NOTIFYs */ ++#define SIP_PAGE3_CISCO_KEEP_CONFERENCE (1 << 14) /*!< DGP: Keep ad-hoc conference after initiator hangs up */ ++#define SIP_PAGE3_CISCO_MULTIADMIN_CONFERENCE (1 << 15) /*!<< DGP: Allow participants to administrate conference */ ++#define SIP_PAGE3_RELAY_NEAREND (1 << 16) /*!< D: Add x-relay-nearend attribute to SDP */ ++#define SIP_PAGE3_RELAY_FAREND (1 << 17) /*!< D: Add x-relay-farend attribute to SDP */ ++#define SIP_PAGE3_SDP_ACK (1 << 18) /*!< D: Add SDP to ACK */ ++#define SIP_PAGE3_CISCO_RECORDING (1 << 19) /*!< D: Call is now being recorded */ ++#define SIP_PAGE3_RTP_STATS_ON_BYE (1 << 20) /*!< D: Display RTP stats on BYE */ ++#define SIP_PAGE3_HUNTGROUP_DEFAULT (1 << 21) /*!< GP: If peer is logged into huntgroup by default */ ++#define SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM (1 << 22) /*!> P: Include the from number in the statusline for pickup notify */ ++#define SIP_PAGE3_CISCO_PICKUPNOTIFY_TO (1 << 23) /*!> P: Include the to number in the statusline for pickup notify */ ++#define SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP (1 << 24) /*!> P: Play a beep for pickup notify */ ++#define SIP_PAGE3_TRANSFER_RESPONSE (1 << 25) /*!< D: Send specific transfer-response for call */ + + #define SIP_PAGE3_FLAGS_TO_COPY \ + (SIP_PAGE3_SNOM_AOC | SIP_PAGE3_SRTP_TAG_32 | SIP_PAGE3_NAT_AUTO_RPORT | SIP_PAGE3_NAT_AUTO_COMEDIA | \ + SIP_PAGE3_DIRECT_MEDIA_OUTGOING | SIP_PAGE3_USE_AVPF | SIP_PAGE3_ICE_SUPPORT | SIP_PAGE3_IGNORE_PREFCAPS | \ +- SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL | SIP_PAGE3_FORCE_AVP | SIP_PAGE3_RTCP_MUX) ++ SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL | SIP_PAGE3_FORCE_AVP | SIP_PAGE3_RTCP_MUX | \ ++ SIP_PAGE3_WHY_DIDNT_DIGIUM_THINK_OF_THIS | SIP_PAGE3_DND_BUSY | SIP_PAGE3_CISCO_KEEP_CONFERENCE | \ ++ SIP_PAGE3_CISCO_MULTIADMIN_CONFERENCE | SIP_PAGE3_HUNTGROUP_DEFAULT | SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM | \ ++ SIP_PAGE3_CISCO_PICKUPNOTIFY_TO | SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP) + + #define CHECK_AUTH_BUF_INITLEN 256 + +@@ -475,6 +494,9 @@ + PIDF_XML, + MWI_NOTIFICATION, + CALL_COMPLETION, ++ PRESENCE, ++ FEATURE_EVENTS, ++ REMOTECC_XML, + }; + + /*! \brief The number of media types in enum \ref media_type below. */ +@@ -940,10 +962,14 @@ + AST_STRING_FIELD(replaces_callid); /*!< Replace info: callid */ + AST_STRING_FIELD(replaces_callid_totag); /*!< Replace info: to-tag */ + AST_STRING_FIELD(replaces_callid_fromtag); /*!< Replace info: from-tag */ ++ AST_STRING_FIELD(require); /*!< Outgoing Require header */ ++ AST_STRING_FIELD(content_id); /*!< Outgoing Content-ID header */ ++ AST_STRING_FIELD(content_type); /*!< Outgoing Content-Type header */ + ); + int attendedtransfer; /*!< Attended or blind transfer? */ + int localtransfer; /*!< Transfer to local domain? */ + enum referstatus status; /*!< REFER status */ ++ struct ast_str *content; /*!< Outgoing content body */ + }; + + /*! \brief Struct to handle custom SIP notify requests. Dynamically allocated when needed */ +@@ -1059,6 +1085,10 @@ + AST_STRING_FIELD(msg_body); /*!< Text for a MESSAGE body */ + AST_STRING_FIELD(tel_phone_context); /*!< The phone-context portion of a TEL URI */ + AST_STRING_FIELD(sessionunique_remote); /*!< Remote UA's SDP Session unique parts */ ++ AST_STRING_FIELD(callforward); /*!< Call Forward target */ ++ AST_STRING_FIELD(join_callid); /*!< Join callid */ ++ AST_STRING_FIELD(join_tag); /*!< Join tag */ ++ AST_STRING_FIELD(join_theirtag); /*!< Join theirtag */ + ); + char via[128]; /*!< Via: header */ + int maxforwards; /*!< SIP Loop prevention */ +@@ -1184,6 +1214,9 @@ + struct ast_sdp_srtp *srtp; /*!< Structure to hold Secure RTP session data for audio */ + struct ast_sdp_srtp *vsrtp; /*!< Structure to hold Secure RTP session data for video */ + struct ast_sdp_srtp *tsrtp; /*!< Structure to hold Secure RTP session data for text */ ++ int donotdisturb:1; /*!< Peer has set DoNotDisturb */ ++ struct sip_pvt *recordoutpvt; /*!< Pvt for the outbound recording leg */ ++ struct sip_pvt *recordinpvt; /*!< Pvt for the inbound recording leg */ + + int red; /*!< T.140 RTP Redundancy */ + int hangupcause; /*!< Storage of hangupcause copied from our owner before we disconnect from the AST channel (only used at hangup) */ +@@ -1217,6 +1250,7 @@ + struct ast_cc_config_params *cc_params; + struct sip_epa_entry *epa_entry; + int fromdomainport; /*!< Domain port to show in from field */ ++ struct sip_conference *conference; /*!< Ad-hoc n-way conference support */ + + struct ast_rtp_dtls_cfg dtls_cfg; + }; +@@ -1266,6 +1300,30 @@ + char id[1]; + }; + ++/*! ++ * \brief A peer's bulk register aliases ++ */ ++struct sip_alias { ++ char *name; ++ AST_LIST_ENTRY(sip_alias) entry; ++ struct sip_peer *peer; ++ int lineindex; ++ unsigned int removed:1; ++}; ++ ++/*! ++ * \brief A peer's subscription ++ */ ++struct sip_subscription { ++ struct sip_pvt *pvt; ++ AST_LIST_ENTRY(sip_subscription) entry; ++ unsigned int removed:1; ++ AST_DECLARE_STRING_FIELDS( ++ AST_STRING_FIELD(exten); ++ AST_STRING_FIELD(context); ++ ); ++}; ++ + /*! \brief Structure for SIP peer data, we place calls to peers if registered or fixed IP address (host) + */ + /* XXX field 'name' must be first otherwise sip_addrcmp() will fail, as will astobj2 hashing of the structure */ +@@ -1301,7 +1359,15 @@ + AST_STRING_FIELD(zone); /*!< Tonezone for this device */ + AST_STRING_FIELD(record_on_feature); /*!< Feature to use when receiving INFO with record: on during a call */ + AST_STRING_FIELD(record_off_feature); /*!< Feature to use when receiving INFO with record: off during a call */ +- AST_STRING_FIELD(callback); /*!< Callback extension */ ++ AST_STRING_FIELD(callbackexten); /*!< Callback extension */ ++ AST_STRING_FIELD(callforward); /*!< Call forwarding extension */ ++ AST_STRING_FIELD(regcallid); /*!< Call-ID of the REGISTER dialog */ ++ AST_STRING_FIELD(cisco_authname); /*!< Name of the primary line */ ++ AST_STRING_FIELD(cisco_softkey); /*!< Name of the last soft-key sent via remotecc */ ++ AST_STRING_FIELD(cisco_devicename); /*!< Name of the device */ ++ AST_STRING_FIELD(cisco_activeload); /*!< Name of the active firmware load */ ++ AST_STRING_FIELD(cisco_inactiveload); /*!< Name of the inactive firmware load */ ++ AST_STRING_FIELD(cisco_qrt_url); /*< QRT URL */ + ); + struct sip_socket socket; /*!< Socket used for this peer */ + enum ast_transport default_outbound_transport; /*!< Peer Registration may change the default outbound transport. +@@ -1324,6 +1390,9 @@ + int inuse; /*!< Number of calls in use */ + int ringing; /*!< Number of calls ringing */ + int onhold; /*!< Peer has someone on hold */ ++ int donotdisturb:1; /*!< Peer has set DoNotDisturb */ ++ int huntgroup:1; /*!< Peer is logged into the HuntGroup */ ++ int offhook; /*!< Peer has signalled that they are off-hook */ + int call_limit; /*!< Limit of concurrent calls */ + unsigned int t38_maxdatagram; /*!< T.38 FaxMaxDatagram override */ + int busy_level; /*!< Level of active channels where we signal busy */ +@@ -1336,6 +1405,15 @@ + /*! Mailboxes that this peer cares about */ + AST_LIST_HEAD_NOLOCK(, sip_mailbox) mailboxes; + ++ /*! Bulk register aliases that this peer cares about */ ++ AST_LIST_HEAD_NOLOCK(, sip_alias) aliases; ++ ++ /*! Subscriptions that this peer cares about */ ++ AST_LIST_HEAD_NOLOCK(, sip_subscription) subscriptions; ++ ++ /*! Dialogs selected for joining */ ++ AST_LIST_HEAD_NOLOCK(, sip_selected) selected; ++ + int maxcallbitrate; /*!< Maximum Bitrate for a video call */ + int expire; /*!< When to expire this peer registration */ + struct ast_format_cap *caps; /*!< Codec capability */ +@@ -1364,11 +1442,16 @@ + struct ast_acl_list *directmediaacl; /*!< Restrict what IPs are allowed to interchange direct media with */ + struct ast_variable *chanvars; /*!< Variables to set for channel created by user */ + struct sip_pvt *mwipvt; /*!< Subscription for MWI */ ++ struct sip_pvt *fepvt; /*!< Subscription for Feature Events */ ++ struct sip_callback *callback; /*!< Extension State watcher for Call Back requests */ + struct sip_st_cfg stimer; /*!< SIP Session-Timers */ + int timer_t1; /*!< The maximum T1 value for the peer */ + int timer_b; /*!< The maximum timer B (transaction timeouts) */ + int fromdomainport; /*!< The From: domain port */ + struct sip_route path; /*!< List of out-of-dialog outgoing routing steps (fm Path headers) */ ++ int cisco_lineindex; /*!< Line index number */ ++ int cisco_pickupnotify_timer; /*!< Toast timer for pickup notify */ ++ time_t cisco_pickupnotify_sent; /*!< Last time a pickup notify was sent */ + + /*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 */ +@@ -1407,7 +1490,7 @@ + AST_STRING_FIELD(hostname); /*!< Domain or host we register to */ + AST_STRING_FIELD(secret); /*!< Password in clear text */ + AST_STRING_FIELD(md5secret); /*!< Password in md5 */ +- AST_STRING_FIELD(callback); /*!< Contact extension */ ++ AST_STRING_FIELD(exten); /*!< Contact extension */ + AST_STRING_FIELD(peername); /*!< Peer registering to */ + AST_STRING_FIELD(localtag); /*!< Local tag generated same time as callid */ + ); +@@ -1448,6 +1531,50 @@ + }; + + /*! ++ * \brief Container for server-side n-way conference bridging ++ */ ++struct sip_conference { ++ int confid; ++ int next_callid; ++ struct ast_bridge *bridge; ++ int keep:1; ++ int multiadmin:1; ++ int administrators; ++ int users; ++ AST_LIST_HEAD_NOLOCK(, sip_participant) participants; ++ AST_LIST_ENTRY(sip_conference) entry; ++}; ++ ++struct sip_participant { ++ int callid; ++ struct sip_conference *conference; ++ struct ast_channel *chan; ++ int administrator:1; ++ int removed:1; ++ int muted:1; ++ int talking:1; ++ AST_LIST_ENTRY(sip_participant) entry; ++}; ++ ++struct sip_selected { ++ AST_DECLARE_STRING_FIELDS( ++ AST_STRING_FIELD(callid); ++ AST_STRING_FIELD(tag); ++ AST_STRING_FIELD(theirtag); ++ ); ++ AST_LIST_ENTRY(sip_selected) entry; ++}; ++ ++/*! ++ * \brief Container for peer callback (camp-on) watcher ++ */ ++struct sip_callback { ++ int stateid; ++ char *exten; ++ int busy:1; ++}; ++ ++/*! + * \brief Definition of an MWI subscription to another server + */ + struct sip_subscription_mwi { +diff -durN asterisk-18.22.0.orig/configs/samples/res_parking.conf.sample asterisk-18.22.0/configs/samples/res_parking.conf.sample +--- asterisk-18.22.0.orig/configs/samples/res_parking.conf.sample 2024-04-04 00:47:56.284994282 +1300 ++++ asterisk-18.22.0/configs/samples/res_parking.conf.sample 2024-04-04 00:48:19.504358686 +1300 +@@ -68,6 +68,7 @@ + ; extensions if parkext is set + + ;parkingtime => 45 ; Number of seconds a call can be parked before returning ++;remindertime => 0 ; Number of seconds before sending a reminder, 0 for no reminder. + + ;comebacktoorigin = yes ; Setting this option configures the behavior of call parking when the + ; parked call times out (See the parkingtime option). The default value is 'yes'. +diff -durN asterisk-18.22.0.orig/configs/samples/sip.conf.sample asterisk-18.22.0/configs/samples/sip.conf.sample +--- asterisk-18.22.0.orig/configs/samples/sip.conf.sample 2024-04-04 00:47:56.284994282 +1300 ++++ asterisk-18.22.0/configs/samples/sip.conf.sample 2024-04-04 00:48:19.508358576 +1300 +@@ -1602,6 +1602,22 @@ + ;defaultip=192.168.0.4 ; IP address to use until registration + ;defaultuser=goran ; Username to use when calling this device before registration + ; Normally you do NOT need to set this parameter ++;dndbusy=yes ; Automatically send back a busy signal when trying to call a peer that is DND ++;huntgroup_default=yes ; If peer is logged into huntgroup by default ++;cisco_usecallmanager=yes ; Enable Cisco phone features, required to make hints, presence and call-forwarding work, ++ ; DO NOT enable this on any other type of device ++;subscribe=123 ; cisco_usecallmanager phones don't SUBSCRIBE to hints so they need to be configured ++ ; both in SEPMAC.cnf.xml and here as well ++;register=cisco2 ; Cisco phones on register their first line, so any additional lines must be ++ ; registered by specifying the name of the peer. ++;cisco_keep_conference=no ; When there are no more administrators in an ad-hoc conference hang up the other ++ ; participants ++;cisco_multiadmin_conference=yes ; If the participant added to an ad-hoc conference has cisco_usecallmanager=yes and ++ ; cisco_multiadmin_conference=yes then make them an administrator as well ++;cisco_pickupnotify_alert=from,to,beep ; Send an alert to the phone if another phone in the pickup group is ringing. ++ ; 'from' - show the caller number, 'to' - show the callee number, ++ ; 'beep' - play a soft beep tone, 'none' - disabled. ++;cisco_pickupnotify_timer=5 ; Display timeout for pickup notify when either 'from' or 'to' are used. + ;setvar=CUSTID=5678 ; Channel variable to be set for all calls from or to this device + ;setvar=ATTENDED_TRANSFER_COMPLETE_SOUND=beep ; This channel variable will + ; cause the given audio file to +diff -durN asterisk-18.22.0.orig/configs/samples/sip_notify.conf.sample asterisk-18.22.0/configs/samples/sip_notify.conf.sample +--- asterisk-18.22.0.orig/configs/samples/sip_notify.conf.sample 2024-04-04 00:47:56.284994282 +1300 ++++ asterisk-18.22.0/configs/samples/sip_notify.conf.sample 2024-04-04 00:48:19.508358576 +1300 +@@ -55,3 +55,34 @@ + + [cisco-check-cfg] + Event=>check-sync ++ ++[cisco-restart] ++Event=>service-control ++Subscription-State=>active ++Content-Type=>text/plain ++Content=>action=restart ++Content=>RegisterCallId={${SIPPEER(${PEERNAME},regcallid)}} ++Content=>ConfigVersionStamp={00000000-0000-0000-0000-000000000000} ++Content=>DialplanVersionStamp={00000000-0000-0000-0000-000000000000} ++Content=>SoftkeyVersionStamp={00000000-0000-0000-0000-000000000000} ++Content=>FeatureControlVersionStamp={00000000-0000-0000-0000-000000000000} ++Content=>HeadsetVersionStamp={0-0000000000} ++ ++[cisco-reset] ++Event=>service-control ++Subscription-State=>active ++Content-Type=>text/plain ++Content=>action=reset ++Content=>RegisterCallId={${SIPPEER(${PEERNAME},regcallid)}} ++Content=>ConfigVersionStamp={00000000-0000-0000-0000-000000000000} ++Content=>DialplanVersionStamp={00000000-0000-0000-0000-000000000000} ++Content=>SoftkeyVersionStamp={00000000-0000-0000-0000-000000000000} ++Content=>FeatureControlVersionStamp={00000000-0000-0000-0000-000000000000} ++Content=>HeadsetVersionStamp={0-0000000000} ++ ++[cisco-prt-report] ++Event=>service-control ++Subscription-State=>active ++Content-Type=>text/plain ++Content=>action=prt-report ++Content=>RegisterCallId={${SIPPEER(${PEERNAME},regcallid)}} +diff -durN asterisk-18.22.0.orig/contrib/realtime/mysql/mysql_config.sql asterisk-18.22.0/contrib/realtime/mysql/mysql_config.sql +--- asterisk-18.22.0.orig/contrib/realtime/mysql/mysql_config.sql 2024-04-04 00:47:56.308993625 +1300 ++++ asterisk-18.22.0/contrib/realtime/mysql/mysql_config.sql 2024-04-04 00:48:19.508358576 +1300 +@@ -95,6 +95,9 @@ + dynamic ENUM('yes','no'), + path VARCHAR(256), + supportpath ENUM('yes','no'), ++ donotdisturb ENUM('yes','no'), ++ callforward VARCHAR(40), ++ huntgroup ENUM('yes','no'), + PRIMARY KEY (id), + UNIQUE (name) + ); +diff -durN asterisk-18.22.0.orig/contrib/realtime/postgresql/postgresql_config.sql asterisk-18.22.0/contrib/realtime/postgresql/postgresql_config.sql +--- asterisk-18.22.0.orig/contrib/realtime/postgresql/postgresql_config.sql 2024-04-04 00:47:56.308993625 +1300 ++++ asterisk-18.22.0/contrib/realtime/postgresql/postgresql_config.sql 2024-04-04 00:48:19.508358576 +1300 +@@ -115,6 +115,9 @@ + dynamic yes_no_values, + path VARCHAR(256), + supportpath yes_no_values, ++ donotdisturb yes_no_values, ++ callforward VARCHAR(40), ++ huntgroup yes_no_values, + PRIMARY KEY (id), + UNIQUE (name) + ); +diff -durN asterisk-18.22.0.orig/include/asterisk/callerid.h asterisk-18.22.0/include/asterisk/callerid.h +--- asterisk-18.22.0.orig/include/asterisk/callerid.h 2024-04-04 00:47:56.336992859 +1300 ++++ asterisk-18.22.0/include/asterisk/callerid.h 2024-04-04 00:48:19.508358576 +1300 +@@ -543,7 +543,11 @@ + /*! Update from call transfer(active) (Party has already answered) */ + AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER, + /*! Update from call transfer(alerting) (Party has not answered yet) */ +- AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING ++ AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING, ++ /*! Update from a call being parked */ ++ AST_CONNECTED_LINE_UPDATE_SOURCE_PARKED_CALL, ++ /*! Update from a call joining a conference */ ++ AST_CONNECTED_LINE_UPDATE_SOURCE_CONFERENCE + }; + + /*! +diff -durN asterisk-18.22.0.orig/include/asterisk/parking.h asterisk-18.22.0/include/asterisk/parking.h +--- asterisk-18.22.0.orig/include/asterisk/parking.h 2024-04-04 00:47:56.336992859 +1300 ++++ asterisk-18.22.0/include/asterisk/parking.h 2024-04-04 00:48:19.508358576 +1300 +@@ -50,6 +50,7 @@ + PARKED_CALL_UNPARKED, + PARKED_CALL_FAILED, + PARKED_CALL_SWAP, ++ PARKED_CALL_REMINDER, + }; + + /*! +diff -durN asterisk-18.22.0.orig/main/callerid.c asterisk-18.22.0/main/callerid.c +--- asterisk-18.22.0.orig/main/callerid.c 2024-04-04 00:47:56.340992749 +1300 ++++ asterisk-18.22.0/main/callerid.c 2024-04-04 00:48:19.512358466 +1300 +@@ -1373,7 +1373,9 @@ + { AST_CONNECTED_LINE_UPDATE_SOURCE_DIVERSION, "diversion", "Call Diversion (Deprecated, use REDIRECTING)" }, + { AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER, "transfer_active", "Call Transfer(Active)" }, + { AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER, "transfer", "Call Transfer(Active)" },/* Old name must come after new name. */ +- { AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING, "transfer_alerting", "Call Transfer(Alerting)" } ++ { AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING, "transfer_alerting", "Call Transfer(Alerting)" }, ++ { AST_CONNECTED_LINE_UPDATE_SOURCE_PARKED_CALL, "parked_call", "Call Parked" }, ++ { AST_CONNECTED_LINE_UPDATE_SOURCE_CONFERENCE, "conference", "Conference" } + /* *INDENT-ON* */ + }; + +diff -durN asterisk-18.22.0.orig/main/cel.c asterisk-18.22.0/main/cel.c +--- asterisk-18.22.0.orig/main/cel.c 2024-04-04 00:47:56.340992749 +1300 ++++ asterisk-18.22.0/main/cel.c 2024-04-04 00:48:19.512358466 +1300 +@@ -1139,6 +1139,8 @@ + case PARKED_CALL_SWAP: + reason = "ParkedCallSwap"; + break; ++ case PARKED_CALL_REMINDER: ++ return; + } + + if (parked_payload->retriever) { +diff -durN asterisk-18.22.0.orig/main/pbx.c asterisk-18.22.0/main/pbx.c +--- asterisk-18.22.0.orig/main/pbx.c 2024-04-04 00:47:56.344992639 +1300 ++++ asterisk-18.22.0/main/pbx.c 2024-04-04 00:48:19.512358466 +1300 +@@ -5227,7 +5227,7 @@ + return CLI_SUCCESS; + } + /* ... we have hints ... */ +- ast_cli(a->fd, "\n -= Registered Asterisk Dial Plan Hints =-\n"); ++ ast_cli(a->fd, "Location Hints DeviceState PresenceState Watchers\n"); + + i = ao2_iterator_init(hints, 0); + for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) { +@@ -5254,8 +5254,7 @@ + } + ao2_iterator_destroy(&i); + +- ast_cli(a->fd, "----------------\n"); +- ast_cli(a->fd, "- %d hints registered\n", num); ++ ast_cli(a->fd, "%d hints registered\n", num); + return CLI_SUCCESS; + } + +diff -durN asterisk-18.22.0.orig/main/presencestate.c asterisk-18.22.0/main/presencestate.c +--- asterisk-18.22.0.orig/main/presencestate.c 2024-04-04 00:47:56.344992639 +1300 ++++ asterisk-18.22.0/main/presencestate.c 2024-04-04 00:48:19.512358466 +1300 +@@ -72,13 +72,13 @@ + enum ast_presence_state state; + + } state2string[] = { +- { "not_set", AST_PRESENCE_NOT_SET}, +- { "unavailable", AST_PRESENCE_UNAVAILABLE }, +- { "available", AST_PRESENCE_AVAILABLE}, +- { "away", AST_PRESENCE_AWAY}, +- { "xa", AST_PRESENCE_XA}, +- { "chat", AST_PRESENCE_CHAT}, +- { "dnd", AST_PRESENCE_DND}, ++ { "Not_Set", AST_PRESENCE_NOT_SET}, ++ { "Unavailable", AST_PRESENCE_UNAVAILABLE }, ++ { "Available", AST_PRESENCE_AVAILABLE}, ++ { "Away", AST_PRESENCE_AWAY}, ++ { "XA", AST_PRESENCE_XA}, ++ { "Chat", AST_PRESENCE_CHAT}, ++ { "DND", AST_PRESENCE_DND}, + }; + + static struct ast_manager_event_blob *presence_state_to_ami(struct stasis_message *msg); +diff -durN asterisk-18.22.0.orig/res/parking/parking_applications.c asterisk-18.22.0/res/parking/parking_applications.c +--- asterisk-18.22.0.orig/res/parking/parking_applications.c 2024-04-04 00:47:56.352992421 +1300 ++++ asterisk-18.22.0/res/parking/parking_applications.c 2024-04-04 00:48:19.516358356 +1300 +@@ -77,6 +77,11 @@ + Use a timeout of duration seconds instead + of the timeout specified by the parking lot. + ++ + + + +@@ -242,6 +247,7 @@ + OPT_ARG_COMEBACK, + OPT_ARG_TIMEOUT, + OPT_ARG_MUSICONHOLD, ++ OPT_ARG_REMINDER, + OPT_ARG_ARRAY_SIZE /* Always the last element of the enum */ + }; + +@@ -252,6 +258,7 @@ + MUXFLAG_COMEBACK_OVERRIDE = (1 << 3), + MUXFLAG_TIMEOUT_OVERRIDE = (1 << 4), + MUXFLAG_MUSICONHOLD = (1 << 5), ++ MUXFLAG_REMINDER_OVERRIDE = (1 << 6), + }; + + AST_APP_OPTIONS(park_opts, { +@@ -261,6 +268,7 @@ + AST_APP_OPTION_ARG('c', MUXFLAG_COMEBACK_OVERRIDE, OPT_ARG_COMEBACK), + AST_APP_OPTION_ARG('t', MUXFLAG_TIMEOUT_OVERRIDE, OPT_ARG_TIMEOUT), + AST_APP_OPTION_ARG('m', MUXFLAG_MUSICONHOLD, OPT_ARG_MUSICONHOLD), ++ AST_APP_OPTION_ARG('T', MUXFLAG_REMINDER_OVERRIDE, OPT_ARG_REMINDER), + }); + + static int apply_option_timeout (int *var, char *timeout_arg) +@@ -278,8 +286,23 @@ + return 0; + } + ++static int apply_option_reminder (int *var, char *reminder_arg) ++{ ++ if (ast_strlen_zero(reminder_arg)) { ++ ast_log(LOG_ERROR, "No duration value provided for the reminder ('T') option.\n"); ++ return -1; ++ } ++ ++ if (sscanf(reminder_arg, "%d", var) != 1 || *var < 0) { ++ ast_log(LOG_ERROR, "Duration value provided for timeout ('T') option must be 0 or greater.\n"); ++ return -1; ++ } ++ ++ return 0; ++} ++ + static int park_app_parse_data(const char *data, int *disable_announce, int *use_ringing, int *randomize, int *time_limit, +- char **comeback_override, char **lot_name, char **musicclass) ++ int *reminder_delay, char **comeback_override, char **lot_name, char **musicclass) + { + char *parse; + struct ast_flags flags = { 0 }; +@@ -302,6 +325,12 @@ + } + } + ++ if (ast_test_flag(&flags, MUXFLAG_REMINDER_OVERRIDE)) { ++ if (apply_option_reminder(reminder_delay, opts[OPT_ARG_REMINDER])) { ++ return -1; ++ } ++ } ++ + if (ast_test_flag(&flags, MUXFLAG_COMEBACK_OVERRIDE)) { + *comeback_override = ast_strdup(opts[OPT_ARG_COMEBACK]); + } +@@ -368,7 +397,7 @@ + ast_channel_unlock(chan); + } + +-static int setup_park_common_datastore(struct ast_channel *parkee, const char *parker_uuid, const char *comeback_override, int randomize, int time_limit, int silence_announce) ++static int setup_park_common_datastore(struct ast_channel *parkee, const char *parker_uuid, const char *comeback_override, int randomize, int time_limit, int reminder_delay, int silence_announce) + { + struct ast_datastore *datastore = NULL; + struct park_common_datastore *park_datastore; +@@ -420,6 +449,7 @@ + + park_datastore->randomize = randomize; + park_datastore->time_limit = time_limit; ++ park_datastore->reminder_delay = reminder_delay; + park_datastore->silence_announce = silence_announce; + + if (comeback_override) { +@@ -468,6 +498,7 @@ + + data_copy->randomize = data->randomize; + data_copy->time_limit = data->time_limit; ++ data_copy->reminder_delay = data->reminder_delay; + data_copy->silence_announce = data->silence_announce; + + if (data->comeback_override) { +@@ -491,7 +522,7 @@ + + static struct ast_bridge *park_common_setup2(struct ast_channel *parkee, struct ast_channel *parker, + const char *lot_name, const char *comeback_override, const char *musicclass, +- int use_ringing, int randomize, int time_limit, int silence_announcements) ++ int use_ringing, int randomize, int time_limit, int reminder_delay, int silence_announcements) + { + struct ast_bridge *parking_bridge; + RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup); +@@ -531,15 +562,16 @@ + ast_channel_set_bridge_role_option(parkee, "holding_participant", "moh_class", musicclass); + } + setup_park_common_datastore(parkee, ast_channel_uniqueid(parker), comeback_override, randomize, time_limit, +- silence_announcements); ++ reminder_delay, silence_announcements); + return parking_bridge; + } + + struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker, + const char *lot_name, const char *comeback_override, +- int use_ringing, int randomize, int time_limit, int silence_announcements) ++ int use_ringing, int randomize, int time_limit, int reminder_delay, int silence_announcements) + { +- return park_common_setup2(parkee, parker, lot_name, comeback_override, NULL, use_ringing, randomize, time_limit, silence_announcements); ++ return park_common_setup2(parkee, parker, lot_name, comeback_override, NULL, use_ringing, randomize, ++ time_limit, reminder_delay, silence_announcements); + } + + struct ast_bridge *park_application_setup(struct ast_channel *parkee, struct ast_channel *parker, const char *app_data, +@@ -548,17 +580,19 @@ + int use_ringing = 0; + int randomize = 0; + int time_limit = -1; ++ int reminder_delay = -1; + + RAII_VAR(char *, comeback_override, NULL, ast_free); + RAII_VAR(char *, lot_name_app_arg, NULL, ast_free); + RAII_VAR(char *, musicclass, NULL, ast_free); + + if (app_data) { +- park_app_parse_data(app_data, silence_announcements, &use_ringing, &randomize, &time_limit, &comeback_override, &lot_name_app_arg, &musicclass); ++ park_app_parse_data(app_data, silence_announcements, &use_ringing, &randomize, &time_limit, ++ &reminder_delay, &comeback_override, &lot_name_app_arg, &musicclass); + } + + return park_common_setup2(parkee, parker, lot_name_app_arg, comeback_override, musicclass, use_ringing, +- randomize, time_limit, silence_announcements ? *silence_announcements : 0); ++ randomize, time_limit, reminder_delay, silence_announcements ? *silence_announcements : 0); + + } + +diff -durN asterisk-18.22.0.orig/res/parking/parking_bridge.c asterisk-18.22.0/res/parking/parking_bridge.c +--- asterisk-18.22.0.orig/res/parking/parking_bridge.c 2024-04-04 00:47:56.352992421 +1300 ++++ asterisk-18.22.0/res/parking/parking_bridge.c 2024-04-04 00:48:19.516358356 +1300 +@@ -31,6 +31,7 @@ + #include "asterisk/term.h" + #include "asterisk/features.h" + #include "asterisk/bridge_internal.h" ++#include "asterisk/callerid.h" + + struct ast_bridge_parking + { +@@ -104,7 +105,7 @@ + * + * \note ao2_cleanup this reference when you are done using it or you'll cause leaks. + */ +-static struct parked_user *generate_parked_user(struct parking_lot *lot, struct ast_channel *chan, const char *parker_channel_name, const char *parker_dial_string, int use_random_space, int time_limit) ++static struct parked_user *generate_parked_user(struct parking_lot *lot, struct ast_channel *chan, const char *parker_channel_name, const char *parker_dial_string, int use_random_space, int time_limit, int reminder_delay) + { + struct parked_user *new_parked_user; + int preferred_space = -1; /* Initialize to use parking lot defaults */ +@@ -161,6 +162,7 @@ + + new_parked_user->start = ast_tvnow(); + new_parked_user->time_limit = (time_limit >= 0) ? time_limit : lot->cfg->parkingtime; ++ new_parked_user->reminder_delay = (reminder_delay >= 0) ? reminder_delay : lot->cfg->remindertime; + + if (parker_dial_string) { + new_parked_user->parker_dial_string = ast_strdup(parker_dial_string); +@@ -208,6 +210,8 @@ + struct ast_channel_snapshot *parker = NULL; + const char *parker_channel_name = NULL; + RAII_VAR(struct park_common_datastore *, park_datastore, NULL, park_common_datastore_free); ++ char lid_num[16]; ++ struct ast_party_connected_line connected; + + ast_bridge_base_v_table.push(&self->base, bridge_channel, swap); + +@@ -247,6 +251,7 @@ + ast_bridge_channel_unlock(swap); + + parking_set_duration(bridge_channel->features, pu); ++ parking_set_reminder(bridge_channel->features, pu); + + if (parking_channel_set_roles(bridge_channel->chan, self->lot, use_ringing)) { + ast_log(LOG_WARNING, "Failed to apply holding bridge roles to %s while joining the parking lot.\n", +@@ -286,7 +291,7 @@ + } + + pu = generate_parked_user(self->lot, bridge_channel->chan, parker_channel_name, +- park_datastore->parker_dial_string, park_datastore->randomize, park_datastore->time_limit); ++ park_datastore->parker_dial_string, park_datastore->randomize, park_datastore->time_limit, park_datastore->reminder_delay); + ao2_cleanup(parker); + if (!pu) { + publish_parked_call_failure(bridge_channel->chan); +@@ -311,6 +316,7 @@ + + /* Apply parking duration limits */ + parking_set_duration(bridge_channel->features, pu); ++ parking_set_reminder(bridge_channel->features, pu); + + /* Set this to the bridge pvt so that we don't have to refind the parked user associated with this bridge channel again. */ + bridge_channel->bridge_pvt = pu; +@@ -320,6 +326,21 @@ + COLORIZE(COLOR_BRMAGENTA, 0, self->lot->name), + pu->parking_space); + ++ snprintf(lid_num, sizeof(lid_num), "%d", pu->parking_space); ++ ast_party_connected_line_init(&connected); ++ ++ connected.id.name.str = ast_strdup("Park"); ++ connected.id.name.valid = 1; ++ ++ connected.id.number.str = ast_strdup(lid_num); ++ connected.id.number.valid = 1; ++ ++ connected.id.name.presentation = connected.id.number.presentation = AST_PRES_ALLOWED | AST_PRES_USER_NUMBER_PASSED_SCREEN; ++ connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_PARKED_CALL; ++ ++ ast_channel_update_connected_line(bridge_channel->chan, &connected, NULL); ++ ast_party_connected_line_free(&connected); ++ + parking_notify_metermaids(pu->parking_space, self->lot->cfg->parking_con, AST_DEVICE_INUSE); + + return 0; +diff -durN asterisk-18.22.0.orig/res/parking/parking_bridge_features.c asterisk-18.22.0/res/parking/parking_bridge_features.c +--- asterisk-18.22.0.orig/res/parking/parking_bridge_features.c 2024-04-04 00:47:56.352992421 +1300 ++++ asterisk-18.22.0/res/parking/parking_bridge_features.c 2024-04-04 00:48:19.516358356 +1300 +@@ -678,6 +678,15 @@ + return -1; + } + ++static int parking_reminder_callback(struct ast_bridge_channel *bridge_channel, void *hook_pvt) ++{ ++ struct parked_user *user = hook_pvt; ++ ++ publish_parked_call(user, PARKED_CALL_REMINDER); ++ ++ return -1; ++} ++ + void say_parking_space(struct ast_bridge_channel *bridge_channel, const char *payload) + { + unsigned int numeric_value; +@@ -726,6 +735,33 @@ + ao2_ref(user, -1); + } + } ++ ++void parking_set_reminder(struct ast_bridge_features *features, struct parked_user *user) ++{ ++ unsigned int reminder_delay; ++ ++ reminder_delay = user->reminder_delay * 1000; ++ ++ if (!reminder_delay) { ++ /* The is no reminder delay that we need to apply */ ++ return; ++ } ++ ++ /* If the reminder delay has already been passed skip it */ ++ reminder_delay = ast_remaining_ms(user->start, reminder_delay); ++ if (reminder_delay <= 0) { ++ return; ++ } ++ ++ /* The interval hook is going to need a reference to the parked_user */ ++ ao2_ref(user, +1); ++ ++ if (ast_bridge_interval_hook(features, 0, reminder_delay, ++ parking_reminder_callback, user, __ao2_cleanup, AST_BRIDGE_HOOK_REMOVE_ON_PULL)) { ++ ast_log(LOG_ERROR, "Failed to apply reminder delay to the parked call.\n"); ++ ao2_ref(user, -1); ++ } ++} + + /*! \brief Dial plan function to get the parking lot channel of an occupied parking lot */ + static int func_get_parkingslot_channel(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len) +diff -durN asterisk-18.22.0.orig/res/parking/parking_manager.c asterisk-18.22.0/res/parking/parking_manager.c +--- asterisk-18.22.0.orig/res/parking/parking_manager.c 2024-04-04 00:47:56.352992421 +1300 ++++ asterisk-18.22.0/res/parking/parking_manager.c 2024-04-04 00:48:19.516358356 +1300 +@@ -458,7 +458,7 @@ + struct ast_channel *chan, const char *parkinglot, int timeout_override) + { + struct ast_bridge *parking_bridge = park_common_setup(chan, +- chan, parkinglot, NULL, 0, 0, timeout_override, 1); ++ chan, parkinglot, NULL, 0, 0, timeout_override, -1, 1); + + if (!parking_bridge) { + astman_send_error(s, m, "Park action failed\n"); +@@ -667,6 +667,9 @@ + case PARKED_CALL_SWAP: + event_type = "ParkedCallSwap"; + break; ++ case PARKED_CALL_REMINDER: ++ /* PARKED_CALL_REMINDER doesn't currently get a message is is used exclusively for subscriptions */ ++ return; + case PARKED_CALL_FAILED: + /* PARKED_CALL_FAILED doesn't currently get a message and is used exclusively for bridging */ + return; +diff -durN asterisk-18.22.0.orig/res/parking/parking_ui.c asterisk-18.22.0/res/parking/parking_ui.c +--- asterisk-18.22.0.orig/res/parking/parking_ui.c 2024-04-04 00:47:56.352992421 +1300 ++++ asterisk-18.22.0/res/parking/parking_ui.c 2024-04-04 00:48:19.516358356 +1300 +@@ -58,6 +58,7 @@ + ast_cli(fd, "Parking Context : %s\n", lot->cfg->parking_con); + ast_cli(fd, "Parking Spaces : %d-%d\n", lot->cfg->parking_start, lot->cfg->parking_stop); + ast_cli(fd, "Parking Time : %u sec\n", lot->cfg->parkingtime); ++ ast_cli(fd, "Reminder Time : %u sec\n", lot->cfg->remindertime); + ast_cli(fd, "Comeback to Origin : %s\n", lot->cfg->comebacktoorigin ? "yes" : "no"); + ast_cli(fd, "Comeback Context : %s%s\n", lot->cfg->comebackcontext, lot->cfg->comebacktoorigin ? " (comebacktoorigin=yes, not used)" : ""); + ast_cli(fd, "Comeback Dial Time : %u sec\n", lot->cfg->comebackdialtime); +diff -durN asterisk-18.22.0.orig/res/parking/res_parking.h asterisk-18.22.0/res/parking/res_parking.h +--- asterisk-18.22.0.orig/res/parking/res_parking.h 2024-04-04 00:47:56.352992421 +1300 ++++ asterisk-18.22.0/res/parking/res_parking.h 2024-04-04 00:48:19.520358247 +1300 +@@ -67,6 +67,7 @@ + int parking_stop; /*!< Last space in the parking lot */ + + unsigned int parkingtime; /*!< Analogous to parkingtime config option */ ++ unsigned int remindertime; /*!< Analogous to remindertime config option */ + unsigned int comebackdialtime; /*!< Analogous to comebackdialtime config option */ + unsigned int parkfindnext; /*!< Analogous to parkfindnext config option */ + unsigned int parkext_exclusive; /*!< Analogous to parkext_exclusive config option */ +@@ -110,6 +111,7 @@ + char comeback[AST_MAX_CONTEXT]; /*!< Where to go on parking timeout */ + char *parker_dial_string; /*!< dialstring to call back with comebacktoorigin. Used timeout extension generation and call control */ + unsigned int time_limit; /*!< How long this specific channel may remain in the parking lot before timing out */ ++ unsigned int reminder_delay; /*!< How long to wait before sending a reminder */ + struct parking_lot *lot; /*!< Which parking lot the user is parked to */ + enum park_call_resolution resolution; /*!< How did the parking session end? If the call is in a bridge, lock parked_user before checking/setting */ + }; +@@ -268,6 +270,15 @@ + void parking_set_duration(struct ast_bridge_features *features, struct parked_user *user); + + /*! ++ * \since 13.7.2 ++ * \brief Setup a reminder delay feature an ast_bridge_features for parking ++ * ++ * \param features The ast_bridge_features we are establishing the interval hook on ++ * \param user The parked_user receiving the timeout duration limits ++ */ ++void parking_set_reminder(struct ast_bridge_features *features, struct parked_user *user); ++ ++/*! + * \since 12.0.0 + * \brief Get a pointer to the parking lot container for purposes such as iteration + * +@@ -422,7 +433,7 @@ + */ + struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker, + const char *lot_name, const char *comeback_override, +- int use_ringing, int randomize, int time_limit, int silence_announcements); ++ int use_ringing, int randomize, int time_limit, int reminder_delay, int silence_announcements); + + /*! + * \since 12.0.0 +@@ -451,6 +462,7 @@ + char *comeback_override; /*!< Optional goto string for where to send the call after we are done */ + int randomize; /*!< Pick a parking space to enter on at random */ + int time_limit; /*!< time limit override. -1 values don't override, 0 for unlimited time, >0 for custom time limit in seconds */ ++ int reminder_delay; /*!< reminder delay override. -1 values don't override, 0 for none, >0 custom reminder delay in seconds */ + int silence_announce; /*!< Used when a call parks itself to keep it from hearing the parked call announcement */ + }; + +diff -durN asterisk-18.22.0.orig/res/res_format_attr_h264.c asterisk-18.22.0/res/res_format_attr_h264.c +--- asterisk-18.22.0.orig/res/res_format_attr_h264.c 2024-04-04 00:47:56.352992421 +1300 ++++ asterisk-18.22.0/res/res_format_attr_h264.c 2024-04-04 00:48:19.520358247 +1300 +@@ -47,6 +47,7 @@ + * length. It must ALWAYS be a string literal representation of one less than + * H264_MAX_SPS_PPS_SIZE */ + #define H264_MAX_SPS_PPS_SIZE_SCAN_LIMIT "15" ++#define H264_MAX_IMAGEATTR_SIZE 256 + + struct h264_attr { + unsigned int PROFILE_IDC; +@@ -71,6 +72,7 @@ + unsigned int LEVEL_ASYMMETRY_ALLOWED; + char SPS[H264_MAX_SPS_PPS_SIZE]; + char PPS[H264_MAX_SPS_PPS_SIZE]; ++ char IMAGEATTR[H264_MAX_IMAGEATTR_SIZE]; + }; + + static void h264_destroy(struct ast_format *format) +@@ -160,6 +162,12 @@ + ast_copy_string(attr->PPS, attr2->PPS, sizeof(attr->PPS)); + } + ++ if (attr1 && !ast_strlen_zero(attr1->IMAGEATTR)) { ++ ast_copy_string(attr->IMAGEATTR, attr1->IMAGEATTR, sizeof(attr->IMAGEATTR)); ++ } else if (attr2 && !ast_strlen_zero(attr2->IMAGEATTR)) { ++ ast_copy_string(attr->IMAGEATTR, attr2->IMAGEATTR, sizeof(attr->IMAGEATTR)); ++ } ++ + return cloned; + } + +@@ -307,6 +315,42 @@ + return; + } + ++static struct ast_format *h264_attribute_set(const struct ast_format *format, const char *name, const char *value) ++{ ++ struct ast_format *cloned = ast_format_clone(format); ++ struct h264_attr *attr; ++ ++ if (!cloned) { ++ return NULL; ++ } ++ attr = ast_format_get_attribute_data(cloned); ++ ++ if (!strcmp(name, "imageattr")) { ++ ast_copy_string(attr->IMAGEATTR, value, sizeof(attr->IMAGEATTR)); ++ } else { ++ ast_log(LOG_WARNING, "unknown attribute type %s\n", name); ++ } ++ ++ return cloned; ++} ++ ++static const void *h264_attribute_get(const struct ast_format *format, const char *name) ++{ ++ struct h264_attr *attr = ast_format_get_attribute_data(format); ++ ++ if (!attr) { ++ return NULL; ++ } ++ ++ if (!strcmp(name, "imageattr")) { ++ return attr->IMAGEATTR; ++ } else { ++ ast_log(LOG_WARNING, "unknown attribute type %s\n", name); ++ } ++ ++ return NULL; ++} ++ + static struct ast_format_interface h264_interface = { + .format_destroy = h264_destroy, + .format_clone = h264_clone, +@@ -314,6 +358,8 @@ + .format_get_joint = h264_getjoint, + .format_parse_sdp_fmtp = h264_parse_sdp_fmtp, + .format_generate_sdp_fmtp = h264_generate_sdp_fmtp, ++ .format_attribute_set = h264_attribute_set, ++ .format_attribute_get = h264_attribute_get, + }; + + static int unload_module(void) +diff -durN asterisk-18.22.0.orig/res/res_parking.c asterisk-18.22.0/res/res_parking.c +--- asterisk-18.22.0.orig/res/res_parking.c 2024-04-04 00:47:56.356992311 +1300 ++++ asterisk-18.22.0/res/res_parking.c 2024-04-04 00:48:19.520358247 +1300 +@@ -113,6 +113,9 @@ + + Amount of time a call will remain parked before giving up (in seconds). + ++ ++ Amount of time before sending a reminder warning (in seconds). ++ + + Which music class to use for parked calls. They will use the default if unspecified. + +@@ -954,6 +957,7 @@ + cfg->parking_start = source->parking_start; + cfg->parking_stop = source->parking_stop; + cfg->parkingtime = source->parkingtime; ++ cfg->remindertime = source->remindertime; + cfg->comebackdialtime = source->comebackdialtime; + cfg->parkfindnext = source->parkfindnext; + cfg->parkext_exclusive = source->parkext_exclusive; +@@ -1226,6 +1230,7 @@ + aco_option_register(&cfg_info, "parkext", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parkext)); + aco_option_register(&cfg_info, "context", ACO_EXACT, parking_lot_types, "parkedcalls", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parking_con)); + aco_option_register(&cfg_info, "parkingtime", ACO_EXACT, parking_lot_types, "45", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, parkingtime)); ++ aco_option_register(&cfg_info, "remindertime", ACO_EXACT, parking_lot_types, "0", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, remindertime)); + aco_option_register(&cfg_info, "comebacktoorigin", ACO_EXACT, parking_lot_types, "yes", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, comebacktoorigin)); + aco_option_register(&cfg_info, "comebackcontext", ACO_EXACT, parking_lot_types, "parkedcallstimeout", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, comebackcontext)); + aco_option_register(&cfg_info, "comebackdialtime", ACO_EXACT, parking_lot_types, "30", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, comebackdialtime)); diff --git a/asterisk/cisco-usecallmanager-20.7.0.patch b/asterisk/cisco-usecallmanager-20.7.0.patch new file mode 100644 index 0000000..a6b88cb --- /dev/null +++ b/asterisk/cisco-usecallmanager-20.7.0.patch @@ -0,0 +1,8765 @@ +diff -durN asterisk-20.7.0.orig/channels/chan_sip.c asterisk-20.7.0/channels/chan_sip.c +--- asterisk-20.7.0.orig/channels/chan_sip.c 2024-04-04 00:48:07.188695806 +1300 ++++ asterisk-20.7.0/channels/chan_sip.c 2024-04-04 00:48:46.659615343 +1300 +@@ -176,7 +176,7 @@ + /*** MODULEINFO + res_crypto + res_http_websocket +- no ++ yes + deprecated + chan_pjsip + 17 +@@ -369,6 +369,67 @@ + application is only available if TEST_FRAMEWORK is defined. + + ++ ++ ++ Page a series of Cisco USECALLMANAGER phones ++ ++ ++ ++ ++ Name of the SIP peer to page ++ ++ ++ Name of the second peer to page, additional peers are ++ specified as peer&peer2&peer3... ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Using the RTP streaming API, send a request to the specified peers to ++ receive RTP audio. Supported codecs are G711 (mulaw and alaw), G722 and ++ G729a. RTP is transmitted as unicast unless the m() option is used. ++ ++ + + + Gets the specified SIP header from an incoming INVITE message. +@@ -511,6 +572,27 @@ + + Preferred codec index number x (beginning with zero). + ++ ++ The vmexten for this peer. ++ ++ ++ Is DoNotDisturb set on this peer (yes/no). ++ ++ ++ The call forwarding extension for this peer. ++ ++ ++ Is HuntGroup login set on this peer (yes/no). ++ ++ ++ The Call-ID of the REGISTER dialog. ++ ++ ++ The device name of the Cisco USECALLMANAGER peer ++ ++ ++ The line index of the Cisco USECALLMANAGER peer ++ + + + +@@ -729,7 +811,9 @@ + { CPIM_PIDF_XML, "presence", "application/cpim-pidf+xml", "cpim-pidf+xml" }, /* RFC 3863 */ + { PIDF_XML, "presence", "application/pidf+xml", "pidf+xml" }, /* RFC 3863 */ + { XPIDF_XML, "presence", "application/xpidf+xml", "xpidf+xml" }, /* Pre-RFC 3863 with MS additions */ +- { MWI_NOTIFICATION, "message-summary", "application/simple-message-summary", "mwi" } /* RFC 3842: Mailbox notification */ ++ { MWI_NOTIFICATION, "message-summary", "application/simple-message-summary", "mwi" }, /* RFC 3842: Mailbox notification */ ++ { FEATURE_EVENTS, "as-feature-event", "application/x-as-feature-event+xml", "as-feature-event" }, /* EMCA-323 application server feature events */ ++ { REMOTECC_XML, "refer", "application/x-cisco-remotecc-request+xml", "remotecc" } /* Cisco remotecc request/respones */ + }; + + /*! \brief The core structure to setup dialogs. We parse incoming messages by using +@@ -894,6 +978,7 @@ + static struct stasis_subscription *network_change_sub; /*!< subscription id for network change events */ + static struct stasis_subscription *acl_change_sub; /*!< subscription id for named ACL system change events */ + static int network_change_sched_id = -1; ++static struct stasis_subscription *pickup_notify_sub; /*!< subscription id for call ringing events */ + + static char used_context[AST_MAX_CONTEXT]; /*!< name of automatically created context for unloading */ + +@@ -928,6 +1013,68 @@ + */ + static int sipdebug_text; + ++/*! \brief Remotecc applications */ ++enum { ++ REMOTECC_CONFLIST = 1, ++ REMOTECC_CALLBACK = 2 ++}; ++ ++/*! \brief Contains the parsed-out xml elements from a remotecc request */ ++struct remotecc_dialog { ++ char *callid; ++ char *localtag; ++ char *remotetag; ++}; ++ ++struct remotecc_data { ++ char *softkeyevent; ++ struct remotecc_dialog dialogid; ++ struct remotecc_dialog consultdialogid; ++ struct remotecc_dialog joindialogid; ++ int applicationid; ++ int confid; ++ char *usercalldata; ++}; ++ ++/*! \brief Information required to start or join an ad-hoc conference */ ++struct conference_data { ++ struct sip_pvt *pvt; ++ AST_LIST_HEAD_NOLOCK(, sip_selected) selected; ++ int joining:1; ++ AST_DECLARE_STRING_FIELDS( ++ AST_STRING_FIELD(callid); ++ AST_STRING_FIELD(tag); ++ AST_STRING_FIELD(theirtag); ++ AST_STRING_FIELD(join_callid); ++ AST_STRING_FIELD(join_tag); ++ AST_STRING_FIELD(join_theirtag); ++ ); ++}; ++ ++/*! \brief Informtion required to park a call */ ++struct park_data { ++ struct sip_pvt *pvt; ++ struct ast_channel *chan; ++ int monitor:1; ++ AST_DECLARE_STRING_FIELDS( ++ AST_STRING_FIELD(context); ++ AST_STRING_FIELD(callid); ++ AST_STRING_FIELD(tag); ++ AST_STRING_FIELD(theirtag); ++ AST_STRING_FIELD(uniqueid); ++ ); ++}; ++ ++/*! \brief Information required to record a call */ ++struct record_data { ++ int outgoing:1; ++ AST_DECLARE_STRING_FIELDS( ++ AST_STRING_FIELD(callid); ++ AST_STRING_FIELD(tag); ++ AST_STRING_FIELD(theirtag); ++ ); ++}; ++ + static const struct _map_x_s referstatusstrings[] = { + { REFER_IDLE, "" }, + { REFER_SENT, "Request sent" }, +@@ -981,11 +1128,17 @@ + + #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 int presence_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, + }; ++ ++static const struct sip_esc_publish_callbacks presence_esc_publish_callbacks = { ++ .initial_handler = presence_esc_publish_handler, ++ .modify_handler = presence_esc_publish_handler, ++}; + #endif + + /*! +@@ -1008,6 +1161,7 @@ + } event_state_compositors [] = { + #ifdef HAVE_LIBXML2 + {CALL_COMPLETION, "call-completion", &cc_esc_publish_callbacks}, ++ {PRESENCE, "presence", &presence_esc_publish_callbacks}, + #endif + }; + +@@ -1073,6 +1227,10 @@ + static int temp_pvt_init(void *); + static void temp_pvt_cleanup(void *); + ++/*! \brief The ad-hoc conference list */ ++static AST_LIST_HEAD_STATIC(conferences, sip_conference); ++static int next_confid = 0; ++ + /*! \brief A per-thread temporary pvt structure */ + AST_THREADSTORAGE_CUSTOM(ts_temp_pvt, temp_pvt_init, temp_pvt_cleanup); + +@@ -1179,6 +1337,7 @@ + /*--- PBX interface functions */ + static struct ast_channel *sip_request_call(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *dest, int *cause); + static int sip_devicestate(const char *data); ++static int sip_presencestate(const char *data, char **subtype, char **message); + static int sip_sendtext(struct ast_channel *ast, const char *text); + static int sip_call(struct ast_channel *ast, const char *dest, int timeout); + static int sip_sendhtml(struct ast_channel *chan, int subclass, const char *data, int datalen); +@@ -1204,7 +1363,8 @@ + static int sipsock_read(int *id, int fd, short events, void *ignore); + static int __sip_xmit(struct sip_pvt *p, struct ast_str *data); + static int __sip_reliable_xmit(struct sip_pvt *p, uint32_t seqno, int resp, struct ast_str *data, int fatal, int sipmethod); +-static void add_cc_call_info_to_response(struct sip_pvt *p, struct sip_request *resp); ++static void add_cc_call_info(struct sip_request *resp, struct sip_pvt *p); ++static void add_remotecc_call_info(struct sip_request *req, struct sip_pvt *p); + 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 ast_sockaddr *addr, int useglobal_nat, const int intended_method, const struct sip_request *req, const char *msg); +@@ -1214,6 +1374,7 @@ + static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable, int oldsdp, int rpid); + static int transmit_response_with_unsupported(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *unsupported); + static int transmit_response_with_auth(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *rand, enum xmittype reliable, const char *header, int stale); ++static int transmit_response_with_optionsind(struct sip_pvt *p, const struct sip_request *req); + static int transmit_provisional_response(struct sip_pvt *p, const char *msg, const struct sip_request *req, int with_sdp); + static int transmit_response_with_allow(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable); + static void transmit_fake_auth_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable); +@@ -1227,6 +1388,7 @@ + static int transmit_info_with_vidupdate(struct sip_pvt *p); + static int transmit_message(struct sip_pvt *p, int init, int auth); + static int transmit_refer(struct sip_pvt *p, const char *dest); ++static int transmit_refer_with_content(struct sip_pvt *p, const char *type, const char *content); + 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); +@@ -1236,7 +1398,11 @@ + static void copy_request(struct sip_request *dst, const struct sip_request *src); + static void receive_message(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e); + static void parse_moved_contact(struct sip_pvt *p, struct sip_request *req, char **name, char **number, int set_call_forward); +-static int sip_send_mwi_to_peer(struct sip_peer *peer, int cache_only); ++static int sip_send_mwi(struct sip_peer *peer, int cache_only); ++static int sip_send_bulkupdate(struct sip_peer *peer); ++static void extensionstate_subscriptions(struct sip_peer *peer); ++static void register_peer_aliases(struct sip_peer *peer); ++static void expire_peer_aliases(struct sip_peer *peer); + + /* Misc dialog routines */ + static int __sip_autodestruct(const void *data); +@@ -1251,6 +1417,7 @@ + static int build_path(struct sip_pvt *p, struct sip_peer *peer, struct sip_request *req, const char *pathbuf); + static enum check_auth_result register_verify(struct sip_pvt *p, struct ast_sockaddr *addr, + struct sip_request *req, const char *uri); ++struct sip_pvt *get_sip_pvt(const char *callid, const char *totag, const char *fromtag); + static int get_sip_pvt_from_replaces(const char *callid, const char *totag, const char *fromtag, + struct sip_pvt **out_pvt, struct ast_channel **out_chan); + static void check_pendings(struct sip_pvt *p); +@@ -1261,6 +1428,7 @@ + + static int sip_sipredirect(struct sip_pvt *p, const char *dest); + static int is_method_allowed(unsigned int *allowed_methods, enum sipmethod method); ++static void start_record_thread(const char *callid, const char *tag, const char *theirtag, int outgoing); + + /*--- Codec handling / SDP */ + static void try_suggested_sip_codec(struct sip_pvt *p); +@@ -1274,9 +1442,9 @@ + static int process_sdp_a_ice(const char *a, struct sip_pvt *p, struct ast_rtp_instance *instance, int rtcp_mux); + static int process_sdp_a_rtcp_mux(const char *a, struct sip_pvt *p, int *requested); + static int process_sdp_a_dtls(const char *a, struct sip_pvt *p, struct ast_rtp_instance *instance); +-static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newaudiortp, int *last_rtpmap_codec); +-static int process_sdp_a_video(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newvideortp, int *last_rtpmap_codec); +-static int process_sdp_a_text(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newtextrtp, char *red_fmtp, int *red_num_gen, int *red_data_pt, int *last_rtpmap_codec); ++static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newaudiortp, int *audio_codec, int *rtpmap_codecs); ++static int process_sdp_a_video(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newvideortp, int *video_codec, int *rtpmap_codecs); ++static int process_sdp_a_text(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newtextrtp, char *red_fmtp, int *red_num_gen, int *red_data_pt, int *rtpmap_codecs); + static int process_sdp_a_image(const char *a, struct sip_pvt *p); + static void add_ice_to_sdp(struct ast_rtp_instance *instance, struct ast_str **a_buf); + static void add_dtls_to_sdp(struct ast_rtp_instance *instance, struct ast_str **a_buf); +@@ -1318,7 +1486,7 @@ + static int expire_register(const void *data); + static void *do_monitor(void *data); + static int restart_monitor(void); +-static void peer_mailboxes_to_str(struct ast_str **mailbox_str, struct sip_peer *peer); ++static void get_peer_mailboxes(struct ast_str **mailbox_str, struct sip_peer *peer); + static struct ast_variable *copy_vars(struct ast_variable *src); + static int dialog_find_multiple(void *obj, void *arg, int flags); + static struct ast_channel *sip_pvt_lock_full(struct sip_pvt *pvt); +@@ -1340,6 +1508,7 @@ + static void mwi_event_cb(void *, struct stasis_subscription *, struct stasis_message *); + static void network_change_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message); + static void acl_change_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message); ++static void pickup_notify_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message); + static void sip_keepalive_all_peers(void); + #define peer_in_destruction(peer) (ao2_ref(peer, 0) == 0) + +@@ -1404,6 +1573,8 @@ + static inline int sip_debug_test_pvt(struct sip_pvt *p); + static void append_history_full(struct sip_pvt *p, const char *fmt, ...); + static void sip_dump_history(struct sip_pvt *dialog); ++static void parse_rtp_stats(struct sip_pvt *pvt, struct sip_request *req); ++static void send_qrt_url(struct sip_peer *peer); + + /*--- Device object handling */ + static struct sip_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime, int devstate_only); +@@ -1414,7 +1585,7 @@ + static struct sip_peer *temp_peer(const char *name); + static void register_peer_exten(struct sip_peer *peer, int onoff); + static int sip_poke_peer_s(const void *data); +-static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, struct sip_peer *p, struct sip_request *req); ++static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, struct sip_peer *p, struct sip_request *req, int *addrchanged); + static void reg_source_db(struct sip_peer *peer); + static void destroy_association(struct sip_peer *peer); + static void set_insecure_flags(struct ast_flags *flags, const char *value, int lineno); +@@ -1469,9 +1640,14 @@ + static int transmit_state_notify(struct sip_pvt *p, struct state_notify_data *data, int full, int timeout); + static void update_connectedline(struct sip_pvt *p, const void *data, size_t datalen); + static void update_redirecting(struct sip_pvt *p, const void *data, size_t datalen); ++static int sip_send_donotdisturb(struct sip_peer *peer); ++static int sip_send_huntgroup(struct sip_peer *peer); ++static int sip_send_callforward(struct sip_peer *peer); + static int get_domain(const char *str, char *domain, int len); + static void get_realm(struct sip_pvt *p, const struct sip_request *req); +-static char *get_content(struct sip_request *req); ++static char *get_content(struct sip_request *req, int start, int end); ++static int find_boundary(struct sip_request *req, const char *boundary, int start, int *done); ++const char *find_content_type(struct sip_request *req); + + /*-- TCP connection handling ---*/ + static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_session); +@@ -1518,7 +1694,7 @@ + static int handle_incoming(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, int *recount, int *nounlock); + static int handle_request_update(struct sip_pvt *p, struct sip_request *req); + static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, int *recount, const char *e, int *nounlock); +-static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint32_t seqno, int *nounlock); ++static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, const char *e, int *nounlock); + static int handle_request_bye(struct sip_pvt *p, struct sip_request *req); + static int handle_request_register(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *sin, const char *e); + static int handle_request_cancel(struct sip_pvt *p, struct sip_request *req); +@@ -1583,6 +1759,7 @@ + .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER, + .requester = sip_request_call, /* called with chan unlocked */ + .devicestate = sip_devicestate, /* called with chan unlocked (not chan-specific) */ ++ .presencestate = sip_presencestate, /* called with chan unlocked (not chan-specific) */ + .call = sip_call, /* called with chan locked */ + .send_html = sip_sendhtml, + .hangup = sip_hangup, /* called with chan locked */ +@@ -3409,9 +3586,23 @@ + if (dialog->relatedpeer && dialog->relatedpeer->mwipvt == dialog) { + dialog->relatedpeer->mwipvt = dialog_unref(dialog->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt"); + } ++ /* Remove link from peer to subscription for Feature Events */ ++ if (dialog->relatedpeer && dialog->relatedpeer->fepvt == dialog) { ++ dialog->relatedpeer->fepvt = dialog_unref(dialog->relatedpeer->fepvt, "delete ->relatedpeer->fepvt"); ++ } + 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 (dialog->conference) { ++ ao2_ref(dialog->conference, -1); ++ dialog->conference = NULL; ++ } ++ if (dialog->recordoutpvt) { ++ dialog->recordoutpvt = dialog_unref(dialog->recordoutpvt, "delete ->recordoutpvt"); ++ } ++ if (dialog->recordinpvt) { ++ dialog->recordinpvt = dialog_unref(dialog->recordinpvt, "delete ->recordinpvt"); ++ } + + dialog_ref(dialog, "Stop scheduled items for unlink action"); + if (ast_sched_add(sched, 0, __dialog_unlink_sched_items, dialog) < 0) { +@@ -5311,6 +5502,85 @@ + } + } + ++static void destroy_alias(struct sip_alias *alias) ++{ ++ if (alias->peer) { ++ alias->peer->lastms = 0; ++ ++ if (alias->peer->socket.tcptls_session) { ++ ao2_ref(alias->peer->socket.tcptls_session, -1); ++ } else if (alias->peer->socket.ws_session) { ++ ast_websocket_unref(alias->peer->socket.ws_session); ++ } ++ ++ ast_string_field_set(alias->peer, fullcontact, ""); ++ ast_string_field_set(alias->peer, username, ""); ++ ast_string_field_set(alias->peer, useragent, ""); ++ ++ if (!ast_sockaddr_isnull(&alias->peer->addr)) { ++ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", ++ alias->peer->name); ++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); ++ register_peer_exten(alias->peer, FALSE); ++ ++ memset(&alias->peer->addr, 0, sizeof(alias->peer->addr)); ++ } ++ sip_unref_peer(alias->peer, "destroy_alias removing peer ref"); ++ } ++ ast_free(alias->name); ++ ast_free(alias); ++} ++ ++static void clear_peer_aliases(struct sip_peer *peer) ++{ ++ struct sip_alias *alias; ++ ++ while ((alias = AST_LIST_REMOVE_HEAD(&peer->aliases, entry))) ++ destroy_alias(alias); ++} ++ ++/*! Destroy extension state subscription */ ++static void destroy_subscription(struct sip_subscription *subscription) ++{ ++ if (subscription->pvt) { ++ dialog_unlink_all(subscription->pvt); ++ dialog_unref(subscription->pvt, "destroying subscription"); ++ } ++ ast_string_field_free_memory(subscription); ++ ast_free(subscription); ++} ++ ++/* Destroy all peer-related extension state subscriptions */ ++static void clear_peer_subscriptions(struct sip_peer *peer) ++{ ++ struct sip_subscription *subscription; ++ ++ while ((subscription = AST_LIST_REMOVE_HEAD(&peer->subscriptions, entry))) ++ destroy_subscription(subscription); ++} ++ ++static void destroy_callback(struct sip_peer *peer) ++{ ++ ast_extension_state_del(peer->callback->stateid, NULL); ++ sip_unref_peer(peer, "destroy_callback: removing callback ref"); ++ ast_free(peer->callback->exten); ++ ast_free(peer->callback); ++} ++ ++static void destroy_selected(struct sip_selected *selected) ++{ ++ ast_string_field_free_memory(selected); ++ ast_free(selected); ++} ++ ++static void clear_peer_selected(struct sip_peer *peer) ++{ ++ struct sip_selected *selected; ++ ++ while ((selected = AST_LIST_REMOVE_HEAD(&peer->selected, entry))) ++ destroy_selected(selected); ++} ++ + static void sip_destroy_peer_fn(void *peer) + { + sip_destroy_peer(peer); +@@ -5327,6 +5597,9 @@ + * happening right now. + */ + clear_peer_mailboxes(peer); ++ clear_peer_aliases(peer); ++ clear_peer_subscriptions(peer); ++ clear_peer_selected(peer); + + if (peer->outboundproxy) { + ao2_ref(peer->outboundproxy, -1); +@@ -5344,6 +5617,16 @@ + peer->mwipvt = dialog_unref(peer->mwipvt, "unreffing peer->mwipvt"); + } + ++ if (peer->fepvt) { /* We have an active subscription, delete it */ ++ dialog_unlink_all(peer->fepvt); ++ peer->fepvt = dialog_unref(peer->fepvt, "unreffing peer->fepvt"); ++ } ++ ++ if (peer->callback) { ++ destroy_callback(peer); ++ peer->callback = NULL; ++ } ++ + if (peer->chanvars) { + ast_variables_destroy(peer->chanvars); + peer->chanvars = NULL; +@@ -6011,6 +6294,31 @@ + } + } + ++static void copy_pvt_data(struct sip_pvt *to_pvt, struct sip_pvt *from_pvt) ++{ ++ sip_pvt_lock(from_pvt); ++ to_pvt->sa = from_pvt->sa; ++ to_pvt->recv = from_pvt->recv; ++ copy_socket_data(&to_pvt->socket, &from_pvt->socket); ++ ast_copy_flags(&to_pvt->flags[0], &from_pvt->flags[0], SIP_FLAGS_TO_COPY); ++ ast_copy_flags(&to_pvt->flags[1], &from_pvt->flags[1], SIP_PAGE2_FLAGS_TO_COPY); ++ ast_copy_flags(&to_pvt->flags[2], &from_pvt->flags[2], SIP_PAGE3_FLAGS_TO_COPY); ++ ++ /* Recalculate our side, and recalculate Call ID */ ++ ast_sip_ouraddrfor(&to_pvt->sa, &to_pvt->ourip, to_pvt); ++ change_callid_pvt(to_pvt, NULL); ++ ++ ast_string_field_set(to_pvt, tohost, from_pvt->tohost); ++ to_pvt->portinuri = from_pvt->portinuri; ++ to_pvt->fromdomainport = from_pvt->fromdomainport; ++ ++ ast_string_field_set(to_pvt, fullcontact, from_pvt->fullcontact); ++ ast_string_field_set(to_pvt, username, from_pvt->username); ++ ast_string_field_set(to_pvt, fromuser, from_pvt->fromuser); ++ ast_string_field_set(to_pvt, fromname, from_pvt->fromname); ++ sip_pvt_unlock(from_pvt); ++} ++ + /*! \brief Initialize DTLS-SRTP support on an RTP instance */ + static int dialog_initialize_dtls_srtp(const struct sip_pvt *dialog, struct ast_rtp_instance *rtp, struct ast_sdp_srtp **srtp) + { +@@ -6314,6 +6622,10 @@ + dialog->fromdomainport = peer->fromdomainport; + } + dialog->callingpres = peer->callingpres; ++ dialog->donotdisturb = peer->donotdisturb; ++ if (!ast_strlen_zero(peer->callforward)) { ++ ast_string_field_set(dialog, callforward, peer->callforward); ++ } + + return 0; + } +@@ -6482,6 +6794,15 @@ + } + } + ++ if (p->donotdisturb && ast_test_flag(&p->flags[2], SIP_PAGE3_DND_BUSY)) { ++ ast_queue_control(p->owner, AST_CONTROL_BUSY); ++ return 0; ++ } else if (!ast_strlen_zero(p->callforward)) { ++ ast_channel_call_forward_set(ast, p->callforward); ++ ast_queue_control(p->owner, AST_CONTROL_BUSY); ++ return 0; ++ } ++ + /* Check whether there is vxml_url, distinctive ring variables */ + headp = ast_channel_varshead(ast); + AST_LIST_TRAVERSE(headp, current, entries) { +@@ -6717,6 +7038,9 @@ + /* Remove link from peer to subscription of MWI */ + if (p->relatedpeer && p->relatedpeer->mwipvt == p) + p->relatedpeer->mwipvt = dialog_unref(p->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt"); ++ /* Remove link from peer to subscription for Feature Events */ ++ if (p->relatedpeer && p->relatedpeer->fepvt) ++ p->relatedpeer->fepvt = dialog_unref(p->relatedpeer->fepvt, "delete ->relatedpeer->fepvt"); + 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"); + +@@ -7255,6 +7579,11 @@ + if (sipdebug) + ast_debug(1, "update_call_counter(%s) - decrement call limit counter on hangup\n", p->username); + update_call_counter(p, DEC_CALL_LIMIT); ++ if (!ast_test_flag(&p->flags[0], SIP_INC_COUNT) && p->relatedpeer) { ++ ao2_lock(p->relatedpeer); ++ clear_peer_selected(p->relatedpeer); ++ ao2_unlock(p->relatedpeer); ++ } + } + + /* Determine how to disconnect */ +@@ -7321,7 +7650,9 @@ + const char *res; + + stop_provisional_keepalive(p); +- if (p->hangupcause && (res = hangup_cause2sip(p->hangupcause))) ++ if (ast_test_flag(&p->flags[2], SIP_PAGE3_TRANSFER_RESPONSE)) ++ transmit_response_reliable(p, "500 Internal Server Error", &p->initreq); ++ else if (p->hangupcause && (res = hangup_cause2sip(p->hangupcause))) + transmit_response_reliable(p, res, &p->initreq); + else + transmit_response_reliable(p, "603 Declined", &p->initreq); +@@ -8144,6 +8475,7 @@ + case AST_CONTROL_UPDATE_RTP_PEER: /* Absorb this since it is handled by the bridge */ + break; + case AST_CONTROL_FLASH: /* We don't currently handle AST_CONTROL_FLASH here, but it is expected, so we don't need to warn either. */ ++ case AST_CONTROL_MCID: + res = -1; + break; + case AST_CONTROL_PVT_CAUSE_CODE: /* these should be handled by the code in channel.c */ +@@ -8616,14 +8948,12 @@ + return __get_header(req, name, &start); + } + +- + AST_THREADSTORAGE(sip_content_buf); + + /*! \brief Get message body content */ +-static char *get_content(struct sip_request *req) ++static char *get_content(struct sip_request *req, int start, int end) + { + struct ast_str *str; +- int i; + + if (!(str = ast_str_thread_get(&sip_content_buf, 128))) { + return NULL; +@@ -8631,8 +8961,8 @@ + + ast_str_reset(str); + +- for (i = 0; i < req->lines; i++) { +- if (ast_str_append(&str, 0, "%s\n", REQ_OFFSET_TO_STR(req, line[i])) < 0) { ++ while (start < req->lines && start <= end) { ++ if (ast_str_append(&str, 0, "%s\n", REQ_OFFSET_TO_STR(req, line[start++])) < 0) { + return NULL; + } + } +@@ -8640,6 +8970,68 @@ + return ast_str_buffer(str); + } + ++/*! \brief find the content boundary */ ++static int find_boundary(struct sip_request *req, const char *boundary, int start, int *done) ++{ ++ char *line; ++ int len = strlen(boundary); ++ ++ for (*done = 0; start < req->lines; start++) { ++ line = REQ_OFFSET_TO_STR(req, line[start]); ++ if (strncmp(line, "--", 2)) { ++ continue; ++ } ++ if (!strncmp(boundary, line + 2, len)) { ++ if (!strcmp(line + 2 + len, "--")) { ++ *done = 1; ++ } else if (strcmp(line + 2 + len, "")) { ++ continue; ++ } ++ return start; ++ } ++ } ++ ++ return -1; ++} ++ ++/*! \brief get the content type from either the Content-Type header or the Content-Type of the first part */ ++const char *find_content_type(struct sip_request *req) ++{ ++ const char *type = sip_get_header(req, "Content-Type"); ++ char *boundary; ++ const char *line; ++ int start, done; ++ ++ if (ast_strlen_zero(type) || strncasecmp(type, "multipart/mixed", 15)) { ++ return type; ++ } ++ ++ if ((boundary = strcasestr(type, ";boundary="))) { ++ boundary += 10; ++ } else if ((boundary = strcasestr(type, "; boundary="))) { ++ boundary += 11; ++ } else { ++ return ""; ++ } ++ boundary = ast_strdupa(boundary); ++ boundary = strsep(&boundary, ";"); ++ ++ if ((start = find_boundary(req, boundary, 0, &done)) == -1) { ++ return ""; ++ } ++ ++ for (++start; start < req->lines; start++) { ++ line = REQ_OFFSET_TO_STR(req, line[start]); ++ if (!strncasecmp(line, "Content-Type:", 13)) { ++ return ast_skip_blanks(line + 13); ++ } else if (!strcasecmp(line, "")) { ++ break; ++ } ++ } ++ ++ return ""; ++} ++ + /*! \brief Read RTP from network */ + static struct ast_frame *sip_rtp_read(struct ast_channel *ast, struct sip_pvt *p, int *faxdetect) + { +@@ -10322,7 +10714,9 @@ + /* Others */ + int sendonly = -1; + unsigned int numberofports; +- int last_rtpmap_codec = 0; ++ int audio_codec = 255; ++ int video_codec = 255; ++ int rtpmap_codecs = 0; + int red_data_pt[10]; /* For T.140 RED */ + int red_num_gen = 0; /* For T.140 RED */ + char red_fmtp[100] = "empty"; /* For T.140 RED */ +@@ -10390,11 +10784,11 @@ + if (process_sdp_a_sendonly(value, &sendonly)) { + processed = TRUE; + } +- else if (process_sdp_a_audio(value, p, &newaudiortp, &last_rtpmap_codec)) ++ else if (process_sdp_a_audio(value, p, &newaudiortp, &audio_codec, &rtpmap_codecs)) + processed = TRUE; +- else if (process_sdp_a_video(value, p, &newvideortp, &last_rtpmap_codec)) ++ else if (process_sdp_a_video(value, p, &newvideortp, &video_codec, &rtpmap_codecs)) + processed = TRUE; +- else if (process_sdp_a_text(value, p, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &last_rtpmap_codec)) ++ else if (process_sdp_a_text(value, p, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &rtpmap_codecs)) + processed = TRUE; + else if (process_sdp_a_image(value, p)) + processed = TRUE; +@@ -10851,7 +11245,7 @@ + ast_log(AST_LOG_NOTICE, "Processed audio crypto attribute without SAVP specified; accepting anyway\n"); + secure_audio = TRUE; + } +- } else if (process_sdp_a_audio(value, p, &newaudiortp, &last_rtpmap_codec)) { ++ } else if (process_sdp_a_audio(value, p, &newaudiortp, &audio_codec, &rtpmap_codecs)) { + processed = TRUE; + } else if (process_sdp_a_rtcp_mux(value, p, &remote_rtcp_mux_audio)) { + processed = TRUE; +@@ -10874,7 +11268,7 @@ + ast_log(AST_LOG_NOTICE, "Processed video crypto attribute without SAVP specified; accepting anyway\n"); + secure_video = TRUE; + } +- } else if (process_sdp_a_video(value, p, &newvideortp, &last_rtpmap_codec)) { ++ } else if (process_sdp_a_video(value, p, &newvideortp, &video_codec, &rtpmap_codecs)) { + processed = TRUE; + } else if (process_sdp_a_rtcp_mux(value, p, &remote_rtcp_mux_video)) { + processed = TRUE; +@@ -10884,7 +11278,7 @@ + else if (text) { + if (process_sdp_a_ice(value, p, p->trtp, rtcp_mux_offered)) { + processed = TRUE; +- } else if (process_sdp_a_text(value, p, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &last_rtpmap_codec)) { ++ } else if (process_sdp_a_text(value, p, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &rtpmap_codecs)) { + processed = TRUE; + } else if (!processed_crypto && process_crypto(p, p->trtp, &p->tsrtp, value)) { + processed_crypto = TRUE; +@@ -11566,11 +11960,11 @@ + return found; + } + +-static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newaudiortp, int *last_rtpmap_codec) ++static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newaudiortp, int *audio_codec, int *rtpmap_codecs) + { + int found = FALSE; + unsigned int codec; +- char mimeSubtype[128]; ++ char mime_subtype[128]; + char fmtp_string[256]; + unsigned int sample_rate; + int debug = sip_debug_test_pvt(p); +@@ -11593,24 +11987,24 @@ + ast_rtp_codecs_set_framing(newaudiortp, framing); + } + found = TRUE; +- } else if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mimeSubtype, &sample_rate) == 3) { ++ } else if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mime_subtype, &sample_rate) == 3) { + /* We have a rtpmap to handle */ +- if (*last_rtpmap_codec < SDP_MAX_RTPMAP_CODECS) { +- if (!(ast_rtp_codecs_payloads_set_rtpmap_type_rate(newaudiortp, NULL, codec, "audio", mimeSubtype, ++ if (*rtpmap_codecs < SDP_MAX_RTPMAP_CODECS) { ++ if (!(ast_rtp_codecs_payloads_set_rtpmap_type_rate(newaudiortp, NULL, codec, "audio", mime_subtype, + ast_test_flag(&p->flags[0], SIP_G726_NONSTANDARD) ? AST_RTP_OPT_G726_NONSTANDARD : 0, sample_rate))) { + if (debug) +- ast_verbose("Found audio description format %s for ID %u\n", mimeSubtype, codec); +- //found_rtpmap_codecs[last_rtpmap_codec] = codec; +- (*last_rtpmap_codec)++; ++ ast_verbose("Found audio description format %s for ID %u\n", mime_subtype, codec); ++ *audio_codec = codec; ++ (*rtpmap_codecs)++; + found = TRUE; + } else { + ast_rtp_codecs_payloads_unset(newaudiortp, NULL, codec); + if (debug) +- ast_verbose("Found unknown media description format %s for ID %u\n", mimeSubtype, codec); ++ ast_verbose("Found unknown media description format %s for ID %u\n", mime_subtype, codec); + } + } else { + if (debug) +- ast_verbose("Discarded description format %s for ID %u\n", mimeSubtype, codec); ++ ast_verbose("Discarded description format %s for ID %u\n", mime_subtype, codec); + } + } else if (sscanf(a, "fmtp: %30u %255[^\t\n]", &codec, fmtp_string) == 2) { + struct ast_format *format; +@@ -11646,36 +12040,37 @@ + return found; + } + +-static int process_sdp_a_video(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newvideortp, int *last_rtpmap_codec) ++static int process_sdp_a_video(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newvideortp, int *video_codec, int *rtpmap_codecs) + { + int found = FALSE; + unsigned int codec; +- char mimeSubtype[128]; ++ char mime_subtype[128]; + unsigned int sample_rate; + int debug = sip_debug_test_pvt(p); + char fmtp_string[256]; ++ char imageattr[256]; + +- if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mimeSubtype, &sample_rate) == 3) { ++ if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mime_subtype, &sample_rate) == 3) { + /* We have a rtpmap to handle */ +- if (*last_rtpmap_codec < SDP_MAX_RTPMAP_CODECS) { ++ if (*rtpmap_codecs < SDP_MAX_RTPMAP_CODECS) { + /* Note: should really look at the '#chans' params too */ +- if (!strncasecmp(mimeSubtype, "H26", 3) || !strncasecmp(mimeSubtype, "MP4", 3) +- || !strncasecmp(mimeSubtype, "VP8", 3)) { +- if (!(ast_rtp_codecs_payloads_set_rtpmap_type_rate(newvideortp, NULL, codec, "video", mimeSubtype, 0, sample_rate))) { ++ if (!strncasecmp(mime_subtype, "H26", 3) || !strncasecmp(mime_subtype, "MP4", 3) ++ || !strncasecmp(mime_subtype, "VP8", 3)) { ++ if (!(ast_rtp_codecs_payloads_set_rtpmap_type_rate(newvideortp, NULL, codec, "video", mime_subtype, 0, sample_rate))) { + if (debug) +- ast_verbose("Found video description format %s for ID %u\n", mimeSubtype, codec); +- //found_rtpmap_codecs[last_rtpmap_codec] = codec; +- (*last_rtpmap_codec)++; ++ ast_verbose("Found video description format %s for ID %u\n", mime_subtype, codec); ++ *video_codec = codec; ++ (*rtpmap_codecs)++; + found = TRUE; + } else { + ast_rtp_codecs_payloads_unset(newvideortp, NULL, codec); + if (debug) +- ast_verbose("Found unknown media description format %s for ID %u\n", mimeSubtype, codec); ++ ast_verbose("Found unknown media description format %s for ID %u\n", mime_subtype, codec); + } + } + } else { + if (debug) +- ast_verbose("Discarded description format %s for ID %u\n", mimeSubtype, codec); ++ ast_verbose("Discarded description format %s for ID %u\n", mime_subtype, codec); + } + } else if (sscanf(a, "fmtp: %30u %255[^\t\n]", &codec, fmtp_string) == 2) { + struct ast_format *format; +@@ -11695,41 +12090,61 @@ + } + ao2_ref(format, -1); + } ++ } else if (sscanf(a, "imageattr: %30u recv %255[^\t\n]", &codec, imageattr) == 2 ++ || (codec = *video_codec, sscanf(a, "imageattr:* recv %255[^\t\n]", imageattr) == 1)) { ++ struct ast_format *format; ++ ++ if ((format = ast_rtp_codecs_get_payload_format(newvideortp, codec))) { ++ struct ast_format *format_parsed; ++ ++ format_parsed = ast_format_attribute_set(format, "imageattr", imageattr); ++ ++ if (format_parsed) { ++ ast_rtp_codecs_payload_replace_format(newvideortp, codec, format_parsed); ++ ao2_replace(format, format_parsed); ++ ao2_ref(format_parsed, -1); ++ found = TRUE; ++ } else { ++ ast_rtp_codecs_payloads_unset(newvideortp, NULL, codec); ++ } ++ ao2_ref(format, -1); ++ } + } + + return found; + } + +-static int process_sdp_a_text(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newtextrtp, char *red_fmtp, int *red_num_gen, int *red_data_pt, int *last_rtpmap_codec) ++static int process_sdp_a_text(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newtextrtp, char *red_fmtp, int *red_num_gen, int *red_data_pt, int *rtpmap_codecs) + { + int found = FALSE; + unsigned int codec; +- char mimeSubtype[128]; ++ char mime_subtype[128]; + unsigned int sample_rate; + char *red_cp; + int debug = sip_debug_test_pvt(p); + +- if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mimeSubtype, &sample_rate) == 3) { ++ if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mime_subtype, &sample_rate) == 3) { + /* We have a rtpmap to handle */ +- if (*last_rtpmap_codec < SDP_MAX_RTPMAP_CODECS) { +- if (!strncasecmp(mimeSubtype, "T140", 4)) { /* Text */ ++ if (*rtpmap_codecs < SDP_MAX_RTPMAP_CODECS) { ++ if (!strncasecmp(mime_subtype, "T140", 4)) { /* Text */ + if (p->trtp) { +- /* ast_verbose("Adding t140 mimeSubtype to textrtp struct\n"); */ +- ast_rtp_codecs_payloads_set_rtpmap_type_rate(newtextrtp, NULL, codec, "text", mimeSubtype, 0, sample_rate); ++ /* ast_verbose("Adding t140 mime_subtype to textrtp struct\n"); */ ++ ast_rtp_codecs_payloads_set_rtpmap_type_rate(newtextrtp, NULL, codec, "text", mime_subtype, 0, sample_rate); + found = TRUE; + } +- } else if (!strncasecmp(mimeSubtype, "RED", 3)) { /* Text with Redudancy */ ++ } else if (!strncasecmp(mime_subtype, "RED", 3)) { /* Text with Redudancy */ + if (p->trtp) { +- ast_rtp_codecs_payloads_set_rtpmap_type_rate(newtextrtp, NULL, codec, "text", mimeSubtype, 0, sample_rate); ++ ast_rtp_codecs_payloads_set_rtpmap_type_rate(newtextrtp, NULL, codec, "text", mime_subtype, 0, sample_rate); + sprintf(red_fmtp, "fmtp:%u ", codec); + if (debug) + ast_verbose("RED submimetype has payload type: %u\n", codec); + found = TRUE; + } + } ++ (*rtpmap_codecs)++; + } else { + if (debug) +- ast_verbose("Discarded description format %s for ID %u\n", mimeSubtype, codec); ++ ast_verbose("Discarded description format %s for ID %u\n", mime_subtype, codec); + } + } else if (!strncmp(a, red_fmtp, strlen(red_fmtp))) { + char *rest = NULL; +@@ -11870,15 +12285,20 @@ + * is supported for this dialog. */ + static int add_supported(struct sip_pvt *pvt, struct sip_request *req) + { +- char supported_value[SIPBUFSIZE]; +- int res; ++ struct ast_str *supported = ast_str_alloca(128); + +- sprintf(supported_value, "replaces%s%s", +- (st_get_mode(pvt, 0) != SESSION_TIMER_MODE_REFUSE) ? ", timer" : "", +- ast_test_flag(&pvt->flags[0], SIP_USEPATH) ? ", path" : ""); +- res = add_header(req, "Supported", supported_value); ++ ast_str_append(&supported, -1, "replaces"); ++ if (st_get_mode(pvt, 0) != SESSION_TIMER_MODE_REFUSE) { ++ ast_str_append(&supported, -1, ",timer"); ++ } ++ if (ast_test_flag(&pvt->flags[0], SIP_USEPATH)) { ++ ast_str_append(&supported, -1, ",path"); ++ } ++ if (ast_test_flag(&pvt->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ ast_str_append(&supported, -1, ",X-cisco-sis-10.0.0"); ++ } + +- return res; ++ return add_header(req, "Supported", ast_str_buffer(supported)); + } + + /*! \brief Add header to SIP message */ +@@ -12539,8 +12959,11 @@ + ast_clear_flag(&p->flags[1], SIP_PAGE2_CONNECTLINEUPDATE_PEND); + add_rpid(&resp, p); + } ++ if (p->method == SIP_INVITE) { ++ add_remotecc_call_info(&resp, p); ++ } + if (ast_test_flag(&p->flags[0], SIP_OFFER_CC)) { +- add_cc_call_info_to_response(p, &resp); ++ add_cc_call_info(&resp, p); + } + + /* If we are sending a 302 Redirect we can add a diversion header if the redirect information is set */ +@@ -12787,6 +13210,36 @@ + return send_response(p, &resp, reliable, seqno); + } + ++/*! \brief Respond with an optionind response */ ++static int transmit_response_with_optionsind(struct sip_pvt *p, const struct sip_request *req) ++{ ++ struct sip_request resp; ++ ++ respprep(&resp, p, "200 OK", req); ++ ++ add_header(&resp, "Content-Type", "application/x-cisco-remotecc-response+xml"); ++ add_date(&resp); ++ ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "200\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ add_content(&resp, "\n"); ++ ++ return send_response(p, &resp, XMIT_UNRELIABLE, 0); ++} ++ + /*! + \brief Extract domain from SIP To/From header + \retval -1 on error. +@@ -13009,15 +13462,15 @@ + { + struct ast_str *tmp = ast_str_alloca(256); + char tmp2[256]; +- char lid_name_buf[128]; + char *lid_num; + char *lid_name; + int lid_pres; ++ int lid_source; + const char *fromdomain; +- const char *privacy = NULL; +- const char *screen = NULL; ++ const char *privacy; ++ const char *screen; ++ const char *callback_num; + struct ast_party_id connected_id; +- const char *anonymous_string = "\"Anonymous\" "; + + if (!ast_test_flag(&p->flags[0], SIP_SENDRPID)) { + return 0; +@@ -13026,18 +13479,16 @@ + if (!p->owner) { + return 0; + } ++ ++ lid_source = ast_channel_connected(p->owner)->source; + connected_id = ast_channel_connected_effective_id(p->owner); + lid_num = S_COR(connected_id.number.valid, connected_id.number.str, NULL); +- if (!lid_num) { +- return 0; +- } + lid_name = S_COR(connected_id.name.valid, connected_id.name.str, NULL); +- if (!lid_name) { +- lid_name = lid_num; ++ if (!lid_num && !lid_name) { ++ return 0; + } +- ast_escape_quoted(lid_name, lid_name_buf, sizeof(lid_name_buf)); +- lid_pres = ast_party_id_presentation(&connected_id); + ++ lid_pres = ast_party_id_presentation(&connected_id); + if (((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) && + (ast_test_flag(&p->flags[1], SIP_PAGE2_TRUST_ID_OUTBOUND) == SIP_PAGE2_TRUST_ID_OUTBOUND_NO)) { + /* If pres is not allowed and we don't trust the peer, we don't apply an RPID header */ +@@ -13053,66 +13504,63 @@ + fromdomain = ast_sockaddr_stringify_host_remote(&p->ourip); + } + +- lid_num = ast_uri_encode(lid_num, tmp2, sizeof(tmp2), ast_uri_sip_user); ++ if (!ast_strlen_zero(lid_name)) { ++ if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ if (!strcmp(lid_name, "Conference") && lid_source == AST_CONNECTED_LINE_UPDATE_SOURCE_CONFERENCE) { ++ /* Phone will translate \2004 into the localised version of Conference and enable ConfList/ConfDetails support */ ++ lid_name = "\2004"; ++ } else if (!strcmp(lid_name, "Park") && lid_source == AST_CONNECTED_LINE_UPDATE_SOURCE_PARKED_CALL) { ++ lid_name = "\2005"; ++ } ++ } ++ ++ ast_escape_quoted(lid_name, tmp2, sizeof(tmp2)); ++ ast_str_append(&tmp, -1, "\"%s\" ", tmp2); ++ } ++ ast_str_append(&tmp, -1, "owner, "CISCO_CALLBACK_NUMBER"); ++ if (!ast_strlen_zero(callback_num)) { ++ ast_uri_encode(callback_num, tmp2, sizeof(tmp2), ast_uri_sip_user); ++ ast_str_append(&tmp, -1, ";x-cisco-callback-number=%s", tmp2); ++ } ++ ast_str_append(&tmp, -1, ">"); + + if (ast_test_flag(&p->flags[0], SIP_SENDRPID_PAI)) { + if (ast_test_flag(&p->flags[1], SIP_PAGE2_TRUST_ID_OUTBOUND) != SIP_PAGE2_TRUST_ID_OUTBOUND_LEGACY) { + /* trust_id_outbound = yes - Always give full information even if it's private, but append a privacy header + * When private data is included */ +- ast_str_set(&tmp, -1, "\"%s\" ", lid_name_buf, lid_num, fromdomain); + if ((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { + add_header(req, "Privacy", "id"); + } + } else { + /* trust_id_outbound = legacy - behave in a non RFC-3325 compliant manner and send anonymized data when + * when handling private data. */ +- if ((lid_pres & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED) { +- ast_str_set(&tmp, -1, "\"%s\" ", lid_name_buf, lid_num, fromdomain); +- } else { +- ast_str_set(&tmp, -1, "%s", anonymous_string); ++ if ((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { ++ ast_str_set(&tmp, -1, "\"Anonymous\" "); + } + } + add_header(req, "P-Asserted-Identity", ast_str_buffer(tmp)); + } else { +- ast_str_set(&tmp, -1, "\"%s\" ;party=%s", lid_name_buf, lid_num, fromdomain, p->outgoing_call ? "calling" : "called"); ++ ast_str_append(&tmp, -1, ";party=%s", p->outgoing_call ? "calling" : "called"); + +- switch (lid_pres) { +- case AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED: +- case AST_PRES_ALLOWED_USER_NUMBER_FAILED_SCREEN: +- privacy = "off"; +- screen = "no"; +- break; +- case AST_PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN: +- case AST_PRES_ALLOWED_NETWORK_NUMBER: +- privacy = "off"; +- screen = "yes"; +- break; +- case AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED: +- case AST_PRES_PROHIB_USER_NUMBER_FAILED_SCREEN: +- privacy = "full"; +- screen = "no"; +- break; +- case AST_PRES_PROHIB_USER_NUMBER_PASSED_SCREEN: +- case AST_PRES_PROHIB_NETWORK_NUMBER: ++ if ((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { + privacy = "full"; +- screen = "yes"; +- break; +- case AST_PRES_NUMBER_NOT_AVAILABLE: +- break; +- default: +- if ((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { +- privacy = "full"; +- } +- else +- privacy = "off"; +- screen = "no"; +- break; ++ } else { ++ privacy = "off"; + } + +- if (!ast_strlen_zero(privacy) && !ast_strlen_zero(screen)) { +- ast_str_append(&tmp, -1, ";privacy=%s;screen=%s", privacy, screen); ++ if (lid_pres & AST_PRES_USER_NUMBER_PASSED_SCREEN) { ++ screen = "yes"; ++ } else { ++ screen = "no"; + } + ++ ast_str_append(&tmp, -1, ";privacy=%s;screen=%s", privacy, screen); + add_header(req, "Remote-Party-ID", ast_str_buffer(tmp)); + } + return 0; +@@ -13333,6 +13781,12 @@ + } + + ast_str_append(m_buf, 0, " %d", rtp_code); ++ ++ if (ast_format_cmp(format, ast_format_h264) == AST_FORMAT_CMP_EQUAL && ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ /* Needs to be a large number otherwise video quality is poor */ ++ ast_str_append(a_buf, 0, "b=TIAS:4000000\r\n"); ++ } ++ + ast_str_append(a_buf, 0, "a=rtpmap:%d %s/%u\r\n", rtp_code, subtype, rate); + /* VP8: add RTCP FIR support */ + if (ast_format_cmp(format, ast_format_vp8) == AST_FORMAT_CMP_EQUAL) { +@@ -13340,6 +13794,16 @@ + } + + ast_format_generate_sdp_fmtp(format, rtp_code, a_buf); ++ ++ if (ast_format_cmp(format, ast_format_h264) == AST_FORMAT_CMP_EQUAL && ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ const char *imageattr = (const char *) ast_format_attribute_get(format, "imageattr"); ++ ++ if (ast_strlen_zero(imageattr)) { ++ imageattr = "[x=640,y=480,q=0.50]"; ++ } ++ ++ ast_str_append(a_buf, 0, "a=imageattr:%d recv %s\r\n", rtp_code, imageattr); ++ } + } + + /*! \brief Add text codec offer to SDP offer/answer body in INVITE or 200 OK */ +@@ -13713,7 +14177,7 @@ + ast_test_flag(&p->flags[2], SIP_PAGE3_FORCE_AVP))); + + /* Build max bitrate string */ +- if (p->maxcallbitrate) ++ if (p->maxcallbitrate && !ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) + snprintf(bandwidth, sizeof(bandwidth), "b=CT:%d\r\n", p->maxcallbitrate); + if (debug) { + ast_verbose("Video is at %s\n", ast_sockaddr_stringify(&vdest)); +@@ -13753,6 +14217,11 @@ + } + + /* Start building generic SDP headers */ ++ if (ast_test_flag(&p->flags[2], SIP_PAGE3_RELAY_NEAREND)) { ++ ast_str_append(&a_audio, 0, "a=label:X-relay-nearend\r\n"); ++ } else if (ast_test_flag(&p->flags[2], SIP_PAGE3_RELAY_FAREND)) { ++ ast_str_append(&a_audio, 0, "a=label:X-relay-farend\r\n"); ++ } + + /* We break with the "recommendation" and send our IP, in order that our + peer doesn't have to ast_gethostbyname() us */ +@@ -14099,7 +14568,7 @@ + } + } + +-static void add_cc_call_info_to_response(struct sip_pvt *p, struct sip_request *resp) ++static void add_cc_call_info(struct sip_request *resp, struct sip_pvt *p) + { + char uri[SIPBUFSIZE]; + struct ast_str *header = ast_str_alloca(SIPBUFSIZE); +@@ -14131,6 +14600,52 @@ + ao2_ref(agent, -1); + } + ++static void add_remotecc_call_info(struct sip_request *req, struct sip_pvt *p) ++{ ++ struct ast_str *header = ast_str_alloca(SIPBUFSIZE); ++ ++ if (!ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ return; ++ } ++ ++ ast_str_set(&header, 0, "; orientation=%s; security=", ++ p->outgoing_call ? "from" : "to"); ++ if (p->socket.type & AST_TRANSPORT_TLS) { ++ if (p->srtp) { ++ ast_str_append(&header, 0, "Encrypted"); ++ } else { ++ ast_str_append(&header, 0, "Authenticated"); ++ } ++ } else { ++ ast_str_append(&header, 0, "NotAuthenticated"); ++ } ++ ++ if (ast_test_flag(&p->flags[0], SIP_OUTGOING) && p->owner) { ++ const char *huntpilot = pbx_builtin_getvar_helper(p->owner, "CISCO_HUNTPILOT"); ++ ++ if (!ast_strlen_zero(huntpilot)) { ++ char *huntpilot_copy = ast_strdupa(huntpilot); ++ char *name, *location, tmp[256]; ++ ++ if (!ast_callerid_parse(huntpilot_copy, &name, &location)) { ++ ast_str_append(&header, 0, "; huntpiloturi=\""); ++ ++ if (!ast_strlen_zero(name)) { ++ ast_uri_encode(name, tmp, sizeof(tmp), ast_uri_sip_user); ++ ast_str_append(&header, 0, "%%%02X%s%%%02X ", ++ (unsigned char) '"', tmp, (unsigned char) '"'); ++ } ++ ++ ast_uri_encode(location, tmp, sizeof(tmp), ast_uri_sip_user); ++ ast_str_append(&header, 0, "\"", ++ tmp, ast_sockaddr_stringify_host_remote(&p->ourip)); ++ } ++ } ++ } ++ ++ add_header(req, "Call-Info", ast_str_buffer(header)); ++} ++ + /*! \brief Used for 200 OK and 183 early media + \retval XMIT_ERROR for network errors. + */ +@@ -14145,9 +14660,10 @@ + respprep(&resp, p, msg, req); + if (rpid == TRUE) { + add_rpid(&resp, p); ++ add_remotecc_call_info(&resp, p); + } + if (ast_test_flag(&p->flags[0], SIP_OFFER_CC)) { +- add_cc_call_info_to_response(p, &resp); ++ add_cc_call_info(&resp, p); + } + if (p->rtp) { + ast_rtp_instance_activate(p->rtp); +@@ -14432,8 +14948,6 @@ + const char *d = NULL; /* domain in from header */ + const char *urioptions = ""; + int ourport; +- int cid_has_name = 1; +- int cid_has_num = 1; + struct ast_party_id connected_id; + int ret; + +@@ -14483,7 +14997,7 @@ + + /* Hey, it's a NOTIFY! See if they've configured a mwi_from. + * XXX Right now, this logic works because the only place that mwi_from +- * is set on the sip_pvt is in sip_send_mwi_to_peer. If things changed, then ++ * is set on the sip_pvt is in sip_send_mwi. If things changed, then + * we might end up putting the mwi_from setting into other types of NOTIFY + * messages as well. + */ +@@ -14492,13 +15006,8 @@ + } + + if (ast_strlen_zero(l)) { +- cid_has_num = 0; + l = default_callerid; + } +- if (ast_strlen_zero(n)) { +- cid_has_name = 0; +- n = l; +- } + + /* Allow user to be overridden */ + if (!ast_strlen_zero(p->fromuser)) +@@ -14541,7 +15050,7 @@ + } + + /* If a caller id name was specified, prefix a display name, if there is enough room. */ +- if (cid_has_name || !cid_has_num) { ++ if (!ast_strlen_zero(n)) { + size_t written = ast_str_strlen(from); + size_t name_len; + if (sip_cfg.pedanticsipchecking) { +@@ -14717,9 +15226,45 @@ + quote_str, reason, quote_str); + } + ++ if (ast_test_flag(&pvt->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ int presentation = ast_party_id_presentation(&diverting_from); ++ int header_len = strlen(header_text); ++ const char *privacy; ++ const char *screen; ++ ++ if ((presentation & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { ++ privacy = "full"; ++ } else { ++ privacy = "off"; ++ } ++ ++ if (presentation & AST_PRES_USER_NUMBER_PASSED_SCREEN) { ++ screen = "yes"; ++ } else { ++ screen = "no"; ++ } ++ ++ snprintf(header_text + header_len, sizeof(header_text) - header_len, ++ ";privacy=%s;screen=%s", privacy, screen); ++ } ++ + add_header(req, "Diversion", header_text); + } + ++static void add_join(struct sip_request *req, struct sip_pvt *pvt) ++{ ++ char tmp[256]; ++ ++ if (!ast_test_flag(&pvt->flags[2], SIP_PAGE3_RELAY_NEAREND) && !ast_test_flag(&pvt->flags[2], SIP_PAGE3_RELAY_FAREND)) ++ return; ++ ++ if (ast_strlen_zero(pvt->join_callid) || ast_strlen_zero(pvt->join_tag) || ast_strlen_zero(pvt->join_theirtag)) ++ return; ++ ++ snprintf(tmp, sizeof(tmp), "%s;from-tag=%s;to-tag=%s", pvt->join_callid, pvt->join_tag, pvt->join_theirtag); ++ add_header(req, "Join", tmp); ++} ++ + 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; +@@ -14786,12 +15331,25 @@ + } + add_date(&req); + if (sipmethod == SIP_REFER && p->refer) { /* Call transfer */ ++ if (!ast_strlen_zero(p->refer->require)) { ++ add_header(&req, "Require", p->refer->require); ++ } + if (!ast_strlen_zero(p->refer->refer_to)) { + add_header(&req, "Refer-To", p->refer->refer_to); + } + if (!ast_strlen_zero(p->refer->referred_by)) { + add_header(&req, "Referred-By", p->refer->referred_by); + } ++ if (!ast_strlen_zero(p->refer->content_id)) { ++ add_header(&req, "Content-Id", p->refer->content_id); ++ } ++ if (!ast_strlen_zero(p->refer->content_type)) { ++ add_header(&req, "Content-Type", p->refer->content_type); ++ } ++ if (ast_str_strlen(p->refer->content)) { ++ add_content(&req, ast_str_buffer(p->refer->content)); ++ } ++ add_expires(&req, p->expiry); + } else if (sipmethod == SIP_SUBSCRIBE) { + if (p->subscribed == MWI_NOTIFICATION) { + add_header(&req, "Event", "message-summary"); +@@ -14883,6 +15441,8 @@ + add_rpid(&req, p); + if (sipmethod == SIP_INVITE) { + add_diversion(&req, p); ++ add_join(&req, p); ++ add_remotecc_call_info(&req, p); + } + if (sdp) { + offered_media_list_destroy(p); +@@ -15177,6 +15737,9 @@ + struct ast_channel *c = NULL; + struct timeval tv = {0,}; + ++ if (!device_state_info) ++ return NULL; ++ + /* iterate ringing devices and get the oldest of all causing channels */ + citer = ao2_iterator_init(device_state_info, 0); + for (; (device_state = ao2_iterator_next(&citer)); ao2_ref(device_state, -1)) { +@@ -15195,93 +15758,9 @@ + return c ? ast_channel_ref(c) : NULL; + } + +-/* XXX Candidate for moving into its own file */ +-static int allow_notify_user_presence(struct sip_pvt *p) +-{ +- return (strstr(p->useragent, "Digium")) ? 1 : 0; +-} +- + /*! \brief Builds XML portion of NOTIFY messages for presence or dialog updates */ + static void state_notify_build_xml(struct state_notify_data *data, int full, const char *exten, const char *context, struct ast_str **tmp, struct sip_pvt *p, int subscribed, const char *mfrom, const char *mto) + { +- enum state { NOTIFY_OPEN, NOTIFY_INUSE, NOTIFY_CLOSED } local_state = NOTIFY_OPEN; +- const char *statestring = "terminated"; +- const char *pidfstate = "--"; +- const char *pidfnote ="Ready"; +- char hint[AST_MAX_EXTENSION]; +- +- switch (data->state) { +- case (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE): +- statestring = (sip_cfg.notifyringing == NOTIFYRINGING_ENABLED) ? "early" : "confirmed"; +- local_state = NOTIFY_INUSE; +- pidfstate = "busy"; +- pidfnote = "Ringing"; +- break; +- case AST_EXTENSION_RINGING: +- statestring = "early"; +- local_state = NOTIFY_INUSE; +- pidfstate = "busy"; +- pidfnote = "Ringing"; +- break; +- case AST_EXTENSION_INUSE: +- statestring = "confirmed"; +- local_state = NOTIFY_INUSE; +- pidfstate = "busy"; +- pidfnote = "On the phone"; +- break; +- case AST_EXTENSION_BUSY: +- statestring = "confirmed"; +- local_state = NOTIFY_CLOSED; +- pidfstate = "busy"; +- pidfnote = "On the phone"; +- break; +- case AST_EXTENSION_UNAVAILABLE: +- statestring = "terminated"; +- local_state = NOTIFY_CLOSED; +- pidfstate = "away"; +- pidfnote = "Unavailable"; +- break; +- case AST_EXTENSION_ONHOLD: +- statestring = "confirmed"; +- local_state = NOTIFY_CLOSED; +- pidfstate = "busy"; +- pidfnote = "On hold"; +- break; +- case AST_EXTENSION_NOT_INUSE: +- default: +- /* Default setting */ +- break; +- } +- +- /* Check which device/devices we are watching and if they are registered */ +- if (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, context, exten)) { +- char *hint2; +- char *individual_hint = NULL; +- int hint_count = 0, unavailable_count = 0; +- +- /* strip off any possible PRESENCE providers from hint */ +- if ((hint2 = strrchr(hint, ','))) { +- *hint2 = '\0'; +- } +- hint2 = hint; +- +- while ((individual_hint = strsep(&hint2, "&"))) { +- hint_count++; +- +- if (ast_device_state(individual_hint) == AST_DEVICE_UNAVAILABLE) +- unavailable_count++; +- } +- +- /* If none of the hinted devices are registered, we will +- * override notification and show no availability. +- */ +- if (hint_count > 0 && hint_count == unavailable_count) { +- local_state = NOTIFY_CLOSED; +- pidfstate = "away"; +- pidfnote = "Not online"; +- } +- } +- + switch (subscribed) { + case XPIDF_XML: + case CPIM_PIDF_XML: +@@ -15292,46 +15771,92 @@ + ast_str_append(tmp, 0, "\n", mfrom); + ast_str_append(tmp, 0, "\n", exten); + ast_str_append(tmp, 0, "
\n", mto); +- ast_str_append(tmp, 0, "\n", (local_state == NOTIFY_OPEN) ? "open" : (local_state == NOTIFY_INUSE) ? "inuse" : "closed"); +- ast_str_append(tmp, 0, "\n", (local_state == NOTIFY_OPEN) ? "online" : (local_state == NOTIFY_INUSE) ? "onthephone" : "offline"); ++ if (data->state & (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE) || data->presence_state == AST_PRESENCE_DND) { ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, "\n"); ++ } else if (data->state & (AST_EXTENSION_BUSY | AST_EXTENSION_UNAVAILABLE | AST_EXTENSION_ONHOLD)) { ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, "\n"); ++ } else { ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, "\n"); ++ } + ast_str_append(tmp, 0, "
\n
\n\n"); + break; + case PIDF_XML: /* Eyebeam supports this format */ +- ast_str_append(tmp, 0, +- "\n" +- "\n", mfrom); +- ast_str_append(tmp, 0, "\n"); +- if (pidfstate[0] != '-') { +- ast_str_append(tmp, 0, "\n", pidfstate); +- } +- ast_str_append(tmp, 0, "\n"); +- ast_str_append(tmp, 0, "%s\n", pidfnote); /* Note */ +- ast_str_append(tmp, 0, "\n", exten); /* Tuple start */ +- ast_str_append(tmp, 0, "%s\n", mto); +- if (pidfstate[0] == 'b') /* Busy? Still open ... */ +- ast_str_append(tmp, 0, "open\n"); +- else +- ast_str_append(tmp, 0, "%s\n", (local_state != NOTIFY_CLOSED) ? "open" : "closed"); +- +- if (allow_notify_user_presence(p) && (data->presence_state != AST_PRESENCE_INVALID) +- && (data->presence_state != AST_PRESENCE_NOT_SET)) { ++ if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, ++ "\n", mfrom); ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, "\n"); ++ if (data->state & AST_EXTENSION_RINGING && sip_cfg.notifyringing) { ++ ast_str_append(tmp, 0, "\n"); ++ } else if (data->state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY | AST_EXTENSION_ONHOLD)) { ++ ast_str_append(tmp, 0, "\n"); ++ } else if (data->presence_state == AST_PRESENCE_DND) { ++ ast_str_append(tmp, 0, "\n"); ++ } ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, "\n", p->dialogver); ++ if (data->state == AST_EXTENSION_UNAVAILABLE) { ++ ast_str_append(tmp, 0, "closed\n"); ++ } else { ++ ast_str_append(tmp, 0, "open\n"); ++ } + ast_str_append(tmp, 0, "\n"); +- ast_str_append(tmp, 0, "\n"); +- ast_str_append(tmp, 0, "\n"); +- ast_str_append(tmp, 0, "%s\n", +- ast_presence_state2str(data->presence_state), +- S_OR(data->presence_subtype, ""), +- S_OR(data->presence_message, "")); +- ast_str_append(tmp, 0, "\n"); +- ast_test_suite_event_notify("DIGIUM_PRESENCE_SENT", ++ ast_str_append(tmp, 0, "\n"); ++ } else { ++ ast_str_append(tmp, 0, ++ "\n" ++ "\n", mfrom); ++ ast_str_append(tmp, 0, "\n"); ++ if (data->state == AST_EXTENSION_UNAVAILABLE) { ++ ast_str_append(tmp, 0, "\n"); ++ } else if (data->state & (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE | AST_EXTENSION_BUSY | AST_EXTENSION_ONHOLD) || data->presence_state == AST_PRESENCE_DND) { ++ ast_str_append(tmp, 0, "\n"); ++ } ++ ast_str_append(tmp, 0, "\n"); ++ if (data->state & AST_EXTENSION_RINGING) { ++ ast_str_append(tmp, 0, "Ringing\n"); ++ } else if (data->state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY)) { ++ ast_str_append(tmp, 0, "On the phone\n"); ++ } else if (data->state == AST_EXTENSION_ONHOLD) { ++ ast_str_append(tmp, 0, "On hold\n"); ++ } else if (data->state == AST_EXTENSION_UNAVAILABLE) { ++ ast_str_append(tmp, 0, "Unavailable\n"); ++ } else if (data->presence_state == AST_PRESENCE_DND) { ++ ast_str_append(tmp, 0, "Do not disturb\n"); ++ } else { ++ ast_str_append(tmp, 0, "Ready\n"); ++ } ++ ast_str_append(tmp, 0, "\n", exten); /* Tuple start */ ++ ast_str_append(tmp, 0, "%s\n", mto); ++ if (data->state & (AST_EXTENSION_UNAVAILABLE | AST_EXTENSION_BUSY | AST_EXTENSION_ONHOLD)) { ++ ast_str_append(tmp, 0, "closed\n"); ++ } else { ++ ast_str_append(tmp, 0, "open\n"); ++ } ++ if (ast_test_flag(&p->flags[2], SIP_PAGE3_WHY_DIDNT_DIGIUM_THINK_OF_THIS) && (data->presence_state > 0)) { ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, "\n"); ++ ast_str_append(tmp, 0, "%s\n", ++ ast_presence_state2str(data->presence_state), ++ S_OR(data->presence_subtype, ""), ++ S_OR(data->presence_message, "")); ++ ast_str_append(tmp, 0, "\n"); ++ ast_test_suite_event_notify("DIGIUM_PRESENCE_SENT", + "PresenceState: %s\r\n" + "Subtype: %s\r\n" + "Message: %s", + ast_presence_state2str(data->presence_state), + S_OR(data->presence_subtype, ""), + S_OR(data->presence_message, "")); ++ } ++ ast_str_append(tmp, 0, "\n\n"); + } +- ast_str_append(tmp, 0, "\n\n"); + break; + case DIALOG_INFO_XML: /* SNOM subscribes in this format */ + ast_str_append(tmp, 0, "\n"); +@@ -15441,7 +15966,13 @@ + } else { + ast_str_append(tmp, 0, "\n", exten); + } +- ast_str_append(tmp, 0, "%s\n", statestring); ++ if (data->state & AST_EXTENSION_RINGING && sip_cfg.notifyringing) { ++ ast_str_append(tmp, 0, "early\n"); ++ } else if (data->state & (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE | AST_EXTENSION_BUSY | AST_EXTENSION_ONHOLD) || data->presence_state == AST_PRESENCE_DND) { ++ ast_str_append(tmp, 0, "confirmed\n"); ++ } else { ++ ast_str_append(tmp, 0, "terminated\n"); ++ } + if (data->state == AST_EXTENSION_ONHOLD) { + ast_str_append(tmp, 0, "\n\n" + "\n" +@@ -15536,36 +16067,22 @@ + add_header(&req, "Subscription-State", "terminated;reason=noresource"); + break; + default: +- if (p->expiry) ++ if (p->expiry > 0) { ++ char tmp[64]; ++ ++ snprintf(tmp, sizeof(tmp), "active;expires=%d", p->expiry); + add_header(&req, "Subscription-State", "active"); +- else /* Expired */ ++ } else if (ast_test_flag(&p->flags[2], SIP_PAGE3_SUBSCRIPTIONSTATE_ACTIVE)) { ++ add_header(&req, "Subscription-State", "active"); ++ } else { /* Expired */ + add_header(&req, "Subscription-State", "terminated;reason=timeout"); ++ } + } + +- switch (p->subscribed) { +- case XPIDF_XML: +- case CPIM_PIDF_XML: +- add_header(&req, "Event", subscriptiontype->event); +- state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); +- add_header(&req, "Content-Type", subscriptiontype->mediatype); +- p->dialogver++; +- break; +- case PIDF_XML: /* Eyebeam supports this format */ +- add_header(&req, "Event", subscriptiontype->event); +- state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); +- add_header(&req, "Content-Type", subscriptiontype->mediatype); +- p->dialogver++; +- break; +- case DIALOG_INFO_XML: /* SNOM subscribes in this format */ +- add_header(&req, "Event", subscriptiontype->event); +- state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); +- add_header(&req, "Content-Type", subscriptiontype->mediatype); +- p->dialogver++; +- break; +- case NONE: +- default: +- break; +- } ++ add_header(&req, "Event", subscriptiontype->event); ++ state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); ++ add_header(&req, "Content-Type", subscriptiontype->mediatype); ++ p->dialogver++; + + add_content(&req, ast_str_buffer(tmp)); + +@@ -15626,7 +16143,7 @@ + (0/0) notification. This can temporarily be disabled in + sip.conf with the "buggymwi" option */ + ast_str_append(&out, 0, "Voice-Message: %d/%d%s\r\n", +- newmsgs, oldmsgs, (ast_test_flag(&p->flags[1], SIP_PAGE2_BUGGY_MWI) ? "" : " (0/0)")); ++ newmsgs, oldmsgs, (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) ? "" : " (0/0)")); + + if (p->subscribed) { + if (p->expiry) { +@@ -15785,10 +16302,6 @@ + if (!ast_test_flag(&p->flags[0], SIP_SENDRPID)) { + return; + } +- if (!connected_id.number.valid +- || ast_strlen_zero(connected_id.number.str)) { +- return; +- } + + append_history(p, "ConnectedLine", "%s party is now %s <%s>", + ast_test_flag(&p->flags[0], SIP_OUTGOING) ? "Calling" : "Called", +@@ -15798,7 +16311,12 @@ + if (ast_channel_state(p->owner) == AST_STATE_UP || ast_test_flag(&p->flags[0], SIP_OUTGOING)) { + struct sip_request req; + +- if (!p->pendinginvite && (p->invitestate == INV_CONFIRMED || p->invitestate == INV_TERMINATED)) { ++ if (is_method_allowed(&p->allowed_methods, SIP_UPDATE) && !ast_strlen_zero(p->okcontacturi)) { ++ reqprep(&req, p, SIP_UPDATE, 0, 1); ++ add_rpid(&req, p); ++ add_header(&req, "X-Asterisk-rpid-update", "Yes"); ++ send_request(p, &req, XMIT_CRITICAL, p->ocseq); ++ } else if (!p->pendinginvite && (p->invitestate == INV_CONFIRMED || p->invitestate == INV_TERMINATED)) { + reqprep(&req, p, ast_test_flag(&p->flags[0], SIP_REINVITE_UPDATE) ? SIP_UPDATE : SIP_INVITE, 0, 1); + + add_header(&req, "Allow", ALLOWED_METHODS); +@@ -15810,11 +16328,6 @@ + p->lastinvite = p->ocseq; + ast_set_flag(&p->flags[0], SIP_OUTGOING); + send_request(p, &req, XMIT_CRITICAL, p->ocseq); +- } else if ((is_method_allowed(&p->allowed_methods, SIP_UPDATE)) && (!ast_strlen_zero(p->okcontacturi))) { +- reqprep(&req, p, SIP_UPDATE, 0, 1); +- add_rpid(&req, p); +- add_header(&req, "X-Asterisk-rpid-update", "Yes"); +- send_request(p, &req, XMIT_CRITICAL, p->ocseq); + } else { + /* We cannot send the update yet, so we have to wait until we can */ + ast_set_flag(&p->flags[0], SIP_NEEDREINVITE); +@@ -15843,6 +16356,162 @@ + } + } + ++/*! \brief Notify peer that the do not disturb status has changed */ ++static int sip_send_donotdisturb(struct sip_peer *peer) ++{ ++ if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { ++ return 0; ++ } ++ ++ if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ struct sip_pvt *pvt; ++ struct ast_str *content = ast_str_alloca(8192); ++ ++ if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return -1; ++ } ++ ++ set_socket_transport(&pvt->socket, 0); ++ if (create_addr_from_peer(pvt, peer)) { ++ dialog_unlink_all(pvt); ++ dialog_unref(pvt, "create_addr_from_peer failed in sip_send_donotdisturb. Unref dialog"); ++ return -1; ++ } ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", peer->donotdisturb ? "enable" : "disable"); ++ ast_str_append(&content, 0, "\n", ast_test_flag(&pvt->flags[2], SIP_PAGE3_DND_BUSY) ? "callreject" : "ringeroff"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ transmit_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ } else if (peer->fepvt) { ++ struct sip_request req; ++ struct sip_pvt *pvt = peer->fepvt; ++ char tmp[512]; ++ ++ reqprep(&req, pvt, SIP_NOTIFY, 0, 1); ++ ++ add_header(&req, "Event", "as-feature-event"); ++ if (pvt->expiry) { ++ add_header(&req, "Subscription-State", "active"); ++ } else { ++ add_header(&req, "Subscription-State", "terminated;reason=timeout"); ++ } ++ add_header(&req, "Content-Type", "application/x-as-feature-event+xml"); ++ ++ add_content(&req, "\n"); ++ add_content(&req, "\n"); ++ snprintf(tmp, sizeof(tmp), "\n%s\n", peer->donotdisturb ? "true" : "false"); ++ add_content(&req, tmp); ++ add_content(&req, "\n"); ++ ++ send_request(pvt, &req, XMIT_RELIABLE, pvt->ocseq); ++ } ++ ++ return 0; ++} ++ ++/*! \brief Notify peer that the huntgroup login state has changed */ ++static int sip_send_huntgroup(struct sip_peer *peer) ++{ ++ if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { ++ return 0; ++ } ++ ++ if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ struct sip_pvt *pvt; ++ struct ast_str *content = ast_str_alloca(8192); ++ ++ if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return -1; ++ } ++ ++ set_socket_transport(&pvt->socket, 0); ++ if (create_addr_from_peer(pvt, peer)) { ++ dialog_unlink_all(pvt); ++ dialog_unref(pvt, "create_addr_from_peer failed in sip_send_huntgroup. Unref dialog"); ++ return -1; ++ } ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", peer->huntgroup ? "on" : "off"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ transmit_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ } ++ ++ return 0; ++} ++ ++/*! \brief Notify peer that the call forwarding extension has changed */ ++static int sip_send_callforward(struct sip_peer *peer) ++{ ++ if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { ++ return 0; ++ } ++ ++ if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ struct sip_pvt *pvt; ++ struct ast_str *content = ast_str_alloca(8192); ++ ++ if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return -1; ++ } ++ ++ set_socket_transport(&pvt->socket, 0); ++ if (create_addr_from_peer(pvt, peer)) { ++ dialog_unlink_all(pvt); ++ dialog_unref(pvt, "create_addr_from_peer failed in sip_send_callforward. Unref dialog"); ++ return -1; ++ } ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%d\n", peer->cisco_lineindex); ++ ast_str_append(&content, 0, "%s\n", peer->callforward); ++ ast_str_append(&content, 0, "%s\n", !ast_strlen_zero(peer->vmexten) && !strcmp(peer->callforward, peer->vmexten) ? "on" : "off"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ transmit_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ } else if (peer->fepvt) { ++ struct sip_request req; ++ struct sip_pvt *pvt = peer->fepvt; ++ char tmp[512]; ++ ++ reqprep(&req, pvt, SIP_NOTIFY, 0, 1); ++ ++ add_header(&req, "Event", "as-feature-event"); ++ if (pvt->expiry) { ++ add_header(&req, "Subscription-State", "active"); ++ } else { ++ add_header(&req, "Subscription-State", "terminated;reason=timeout"); ++ } ++ add_header(&req, "Content-Type", "application/x-as-feature-event+xml"); ++ ++ add_content(&req, "\n"); ++ add_content(&req, "\n"); ++ add_content(&req, "\nforwardImmediate\n"); ++ snprintf(tmp, sizeof(tmp), "%s\n%s\n", !ast_strlen_zero(peer->callforward) ? "true" : "false", peer->callforward); ++ add_content(&req, tmp); ++ add_content(&req, "\n"); ++ ++ send_request(pvt, &req, XMIT_RELIABLE, pvt->ocseq); ++ } ++ ++ return 0; ++} ++ + static const struct _map_x_s regstatestrings[] = { + { REG_STATE_FAILED, "Failed" }, + { REG_STATE_UNREGISTERED, "Unregistered"}, +@@ -16256,8 +16925,8 @@ + ast_string_field_set(p, username, r->username); + } + /* Save extension in packet */ +- if (!ast_strlen_zero(r->callback)) { +- ast_string_field_set(p, exten, r->callback); ++ if (!ast_strlen_zero(r->exten)) { ++ ast_string_field_set(p, exten, r->exten); + } + + /* Set transport so the correct contact is built */ +@@ -16383,6 +17052,9 @@ + { + sip_refer_destroy(p); + p->refer = ast_calloc_with_stringfields(1, struct sip_refer, 512); ++ if (p->refer) { ++ p->refer->content = ast_str_create(128); ++ } + return p->refer ? 1 : 0; + } + +@@ -16391,6 +17063,7 @@ + { + if (p->refer) { + ast_string_field_free_memory(p->refer); ++ ast_free(p->refer->content); + ast_free(p->refer); + p->refer = NULL; + } +@@ -16470,6 +17143,28 @@ + */ + } + ++/*! \brief Send an out-of-dialog SIP REFER message with content */ ++static int transmit_refer_with_content(struct sip_pvt *pvt, const char *content_type, const char *content) ++{ ++ /* Refer is outgoing call */ ++ ast_set_flag(&pvt->flags[0], SIP_OUTGOING); ++ sip_refer_alloc(pvt); ++ pvt->refer->status = REFER_SENT; ++ ++ ast_string_field_set(pvt->refer, require, "norefersub"); ++ ast_string_field_set(pvt->refer, referred_by, pvt->our_contact); ++ ast_string_field_build(pvt->refer, content_id, "%08lx", ast_random()); ++ ast_string_field_build(pvt->refer, refer_to, "cid:%s", pvt->refer->content_id); ++ ast_string_field_set(pvt->refer, content_type, content_type); ++ ++ ast_str_set(&pvt->refer->content, 0, "%s", content); ++ ++ sip_scheddestroy(pvt, SIP_TRANS_TIMEOUT); ++ sip_alreadygone(pvt); ++ ++ return transmit_invite(pvt, SIP_REFER, 0, 2, NULL); ++} ++ + /*! \brief Send SIP INFO advice of charge message */ + static int transmit_info_with_aoc(struct sip_pvt *p, struct ast_aoc_decoded *decoded) + { +@@ -16552,6 +17247,10 @@ + + if (sipmethod == SIP_ACK) { + p->invitestate = INV_CONFIRMED; ++ ++ if (ast_test_flag(&p->flags[2], SIP_PAGE3_SDP_ACK)) { ++ add_sdp(&resp, p, FALSE, TRUE, FALSE); ++ } + } + + return send_request(p, &resp, reliable, seqno ? seqno : p->ocseq); +@@ -16658,6 +17357,7 @@ + static int expire_register(const void *data) + { + struct sip_peer *peer = (struct sip_peer *)data; ++ struct sip_subscription *subscription; + + if (!peer) { /* Hmmm. We have no peer. Weird. */ + return 0; +@@ -16665,6 +17365,7 @@ + + peer->expire = -1; + peer->portinuri = 0; ++ ast_string_field_set(peer, regcallid, ""); + + destroy_association(peer); /* remove registration data from storage */ + set_socket_transport(&peer->socket, peer->default_outbound_transport); +@@ -16677,6 +17378,14 @@ + peer->socket.ws_session = NULL; + } + ++ AST_LIST_TRAVERSE(&peer->subscriptions, subscription, entry) { ++ if (subscription->pvt) { ++ dialog_unlink_all(subscription->pvt); ++ dialog_unref(subscription->pvt, "destroying subscription"); ++ subscription->pvt = NULL; ++ } ++ } ++ + if (peer->endpoint) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + ast_endpoint_set_state(peer->endpoint, AST_ENDPOINT_OFFLINE); +@@ -16709,6 +17418,7 @@ + /* Only clear the addr after we check for destruction. The addr must remain + * in order to unlink from the peers_by_ip container correctly */ + memset(&peer->addr, 0, sizeof(peer->addr)); ++ expire_peer_aliases(peer); + + sip_unref_peer(peer, "removing peer ref for expire_register"); + +@@ -16970,7 +17680,7 @@ + } + + /*! \brief Parse contact header and save registration (peer registration) */ +-static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, struct sip_peer *peer, struct sip_request *req) ++static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, struct sip_peer *peer, struct sip_request *req, int *addrchanged) + { + char contact[SIPBUFSIZE]; + char data[SIPBUFSIZE]; +@@ -17047,6 +17757,7 @@ + } + return PARSE_REGISTER_QUERY; + } else if (!strcasecmp(curi, "*") || !expire) { /* Unregister this peer */ ++ pvt->expiry = 0; + /* This means remove all registrations and return OK */ + AST_SCHED_DEL_UNREF(sched, peer->expire, + sip_unref_peer(peer, "remove register expire ref")); +@@ -17184,15 +17895,57 @@ + } + + /* Is this a new IP address for us? */ +- if (ast_sockaddr_cmp(&peer->addr, &oldsin)) { ++ if ((*addrchanged = ast_sockaddr_cmp(&peer->addr, &oldsin))) { + ast_verb(3, "Registered SIP '%s' at %s\n", peer->name, + ast_sockaddr_stringify(&peer->addr)); ++ ++ /* Clear off-hook counter in case of the on-hook notification not being received */ ++ peer->offhook = 0; + } + sip_pvt_unlock(pvt); + sip_poke_peer(peer, 0); + sip_pvt_lock(pvt); + register_peer_exten(peer, 1); + ++ /* Save REGISTER dialog Call-ID */ ++ ast_string_field_set(peer, regcallid, pvt->callid); ++ ++ if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ char reason[SIPBUFSIZE]; ++ ++ ast_copy_string(reason, sip_get_header(req, "Reason"), sizeof(reason)); ++ ++ if (!strncmp(reason, "SIP;cause=200;text=", 19)) { ++ char *devicename, *inactiveload, *activeload, *ext; ++ ++ if ((devicename = strstr(reason, " Name="))) { ++ devicename = ast_strdupa(devicename + 6); ++ devicename = strsep(&devicename, " "); ++ } ++ ++ if ((activeload = strstr(reason, " ActiveLoad=")) || ++ (activeload = strstr(reason, " Load="))) { ++ activeload = ast_strdupa(activeload + (!strncmp(activeload, " ActiveLoad=", 12) ? 12 : 6)); ++ if ((ext = strstr(activeload, ".loads"))) { ++ *ext = '\0'; ++ } ++ activeload = strsep(&activeload, " "); ++ } ++ ++ if ((inactiveload = strstr(reason, " InactiveLoad="))) { ++ inactiveload = ast_strdupa(inactiveload + 14); ++ if ((ext = strstr(inactiveload, ".loads"))) { ++ *ext = '\0'; ++ } ++ inactiveload = strsep(&inactiveload, " "); ++ } ++ ++ ast_string_field_set(peer, cisco_devicename, devicename); ++ ast_string_field_set(peer, cisco_activeload, activeload); ++ ast_string_field_set(peer, cisco_inactiveload, inactiveload); ++ } ++ } ++ + /* Save User agent */ + useragent = sip_get_header(req, "User-Agent"); + if (strcasecmp(useragent, peer->useragent)) { +@@ -17379,6 +18132,8 @@ + /* Always OK if no secret */ + if (ast_strlen_zero(secret) && ast_strlen_zero(md5secret)) { + return AUTH_SUCCESSFUL; ++ } else if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && (sipmethod == SIP_REFER || sipmethod == SIP_PUBLISH)) { ++ return AUTH_SUCCESSFUL; /* Buggy Cisco USECALLMANAGER phones can't auth REFER or PUBLISH correctly */ + } + + /* Always auth with WWW-auth since we're NOT a proxy */ +@@ -17552,7 +18307,7 @@ + } + + if (ast_mwi_state_type() == stasis_message_type(msg)) { +- sip_send_mwi_to_peer(peer, 0); ++ sip_send_mwi(peer, 0); + } + } + +@@ -17609,6 +18364,219 @@ + } + } + ++struct pickup_notify_args { ++ ast_group_t callgroup; ++ struct ast_namedgroups *named_callgroups; ++ time_t now; ++}; ++ ++static int pickup_notify_cb(void *obj, void *arg, int flags) ++{ ++ struct sip_peer *peer = obj; ++ struct pickup_notify_args *args = arg; ++ ++ ao2_lock(peer); ++ ++ if (!ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) || ++ !ast_test_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM | SIP_PAGE3_CISCO_PICKUPNOTIFY_TO | SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP)) { ++ ao2_unlock(peer); ++ return 0; ++ } ++ ++ if (ast_sockaddr_isnull(&peer->addr) || peer->ringing || peer->inuse || peer->donotdisturb) { ++ ao2_unlock(peer); ++ return 0; ++ } ++ ++ if ((peer->pickupgroup & args->callgroup) || ast_namedgroups_intersect(peer->named_pickupgroups, args->named_callgroups)) { ++ if (args->now - peer->cisco_pickupnotify_sent > peer->cisco_pickupnotify_timer) { ++ peer->cisco_pickupnotify_sent = args->now; ++ ao2_unlock(peer); ++ ++ return CMP_MATCH; ++ } ++ } ++ ++ ao2_unlock(peer); ++ ++ return 0; ++} ++ ++static void *pickup_notify_thread(void *obj) ++{ ++ char *device = obj; ++ char match[AST_CHANNEL_NAME]; ++ struct ast_channel_iterator *chaniter; ++ struct ast_channel *pickupchan = NULL, *chan; ++ struct timeval creationtime = { 0, }; ++ struct pickup_notify_args args; ++ char *cid_num, *lid_num; ++ struct ao2_iterator *peeriter; ++ struct sip_peer *peer; ++ struct sip_pvt *pvt; ++ struct ast_str *content = ast_str_alloca(8192); ++ ++ snprintf(match, sizeof(match), "%s-", device); ++ chaniter = ast_channel_iterator_by_name_new(match, strlen(match)); ++ ast_free(device); ++ ++ while ((chan = ast_channel_iterator_next(chaniter))) { ++ ast_channel_lock(chan); ++ ++ /* Pick the youngest ringing channel */ ++ if (ast_channel_state(chan) == AST_STATE_RINGING && ast_tvcmp(ast_channel_creationtime(chan), creationtime) > 0) { ++ if (pickupchan) { ++ ast_channel_unref(pickupchan); ++ } ++ pickupchan = ast_channel_ref(chan); ++ creationtime = ast_channel_creationtime(chan); ++ } ++ ++ ast_channel_unlock(chan); ++ ast_channel_unref(chan); ++ } ++ ++ ast_channel_iterator_destroy(chaniter); ++ ++ if (!pickupchan) { ++ return NULL; ++ } ++ ++ ast_channel_lock(pickupchan); ++ ++ args.callgroup = ast_channel_callgroup(pickupchan); ++ args.named_callgroups = ast_ref_namedgroups(ast_channel_named_callgroups(pickupchan)); ++ ++ cid_num = ast_strdupa(S_COR(ast_channel_caller(pickupchan)->id.number.valid, ast_channel_caller(pickupchan)->id.number.str, "")); ++ lid_num = ast_strdupa(S_COR(ast_channel_connected(pickupchan)->id.number.valid, ast_channel_connected(pickupchan)->id.number.str, CALLERID_UNKNOWN)); ++ ++ ast_channel_unlock(pickupchan); ++ ast_channel_unref(pickupchan); ++ ++ if (!args.callgroup && !args.named_callgroups) { ++ return NULL; ++ } ++ ++ args.now = time(NULL); ++ /* We use ao2_callback here so that we don't hold the lock on the peers container while sending the notify dialogs */ ++ ao2_lock(peers); ++ ++ if (!(peeriter = ao2_callback(peers, OBJ_MULTIPLE, pickup_notify_cb, &args))) { ++ ast_log(LOG_ERROR, "Unable to create iterator for peers container in pickup_notify_thread\n"); ++ ao2_unlock(peers); ++ return NULL; ++ } ++ ++ ao2_unlock(peers); ++ ast_unref_namedgroups(args.named_callgroups); ++ ++ while ((peer = ao2_iterator_next(peeriter))) { ++ if ((!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0)))) { ++ sip_unref_peer(peer, "remove iterator ref"); ++ continue; ++ } ++ ++ set_socket_transport(&pvt->socket, 0); ++ if (create_addr_from_peer(pvt, peer)) { ++ dialog_unlink_all(pvt); ++ dialog_unref(pvt, "create_addr_from_peer failed in pickup_notify_thread. Unref dialog"); ++ sip_unref_peer(peer, "remove iterator ref"); ++ continue; ++ } ++ ++ ast_str_reset(content); ++ ++ if (ast_test_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM | SIP_PAGE3_CISCO_PICKUPNOTIFY_TO)) { ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "notify_display\n"); ++ ast_str_append(&content, 0, ""); ++ if (ast_test_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM)) { ++ ast_str_append(&content, 0, "From %s", lid_num); ++ } ++ if (ast_test_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_TO)) { ++ ast_str_append(&content, 0, "%s %s", ++ ast_test_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM) ? " to" : "To", cid_num); ++ } ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%d\n", peer->cisco_pickupnotify_timer); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "1\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ } ++ ++ if (ast_test_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP)) { ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "DtZipZip\n"); ++ ast_str_append(&content, 0, "all\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ } ++ ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ ++ sip_unref_peer(peer, "remove iterator ref"); ++ } ++ ++ ao2_iterator_destroy(peeriter); ++ ++ return NULL; ++} ++ ++static void pickup_notify_stasis_subscribe(void) ++{ ++ if (!pickup_notify_sub) { ++ pickup_notify_sub = stasis_subscribe(ast_device_state_topic_all(), pickup_notify_stasis_cb, NULL); ++ } ++} ++ ++static void pickup_notify_stasis_unsubscribe(void) ++{ ++ if (pickup_notify_sub) { ++ pickup_notify_sub = stasis_unsubscribe(pickup_notify_sub); ++ } ++} ++ ++static void pickup_notify_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) ++{ ++ struct ast_device_state_message *device_state; ++ char *device; ++ pthread_t threadid; ++ ++ if (stasis_message_type(message) != ast_device_state_message_type()) { ++ return; ++ } ++ ++ device_state = stasis_message_data(message); ++ ++ if (device_state->state != AST_DEVICE_RINGING) { ++ return; ++ } ++ ++ if ((device = ast_strdup(device_state->device)) == NULL) { ++ return; ++ } ++ ++ if (ast_pthread_create_detached_background(&threadid, NULL, pickup_notify_thread, device)) { ++ ast_free(device); ++ } ++} ++ + static void cb_extensionstate_destroy(int id, void *data) + { + struct sip_pvt *p = data; +@@ -17636,8 +18604,8 @@ + /* we must skip the next two checks for a queued state change or resubscribe */ + } else if ((p->laststate == data->state && (~data->state & AST_EXTENSION_RINGING)) && + (p->last_presence_state == data->presence_state && +- !strcmp(p->last_presence_subtype, data->presence_subtype) && +- !strcmp(p->last_presence_message, data->presence_message))) { ++ !strcmp(p->last_presence_subtype, S_OR(data->presence_subtype, "")) && ++ !strcmp(p->last_presence_message, S_OR(data->presence_message, "")))) { + /* don't notify unchanged state or unchanged early-state causing parties again */ + sip_pvt_unlock(p); + return 0; +@@ -17689,7 +18657,7 @@ + } + + if (!force) { +- ast_verb(2, "Extension Changed %s[%s] new state %s for Notify User %s %s\n", exten, context, ast_extension_state2str(data->state), p->username, ++ ast_debug(1, "Extension Changed %s[%s] new state %s for Notify User %s %s\n", exten, context, ast_extension_state2str(data->state), p->username, + ast_test_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE) ? "(queued)" : ""); + } + +@@ -17712,12 +18680,126 @@ + .presence_message = info->presence_message, + }; + +- if ((info->reason == AST_HINT_UPDATE_PRESENCE) && !(allow_notify_user_presence(p))) { +- /* ignore a presence triggered update if we know the useragent doesn't care */ +- return 0; ++ return extensionstate_update(context, exten, ¬ify_data, p, FALSE); ++} ++ ++/*! ++ * \brief Send initial subscription state updates to peer ++ */ ++static void extensionstate_subscriptions(struct sip_peer *peer) ++{ ++ struct sip_subscription *subscription; ++ ++ if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { ++ return; + } + +- return extensionstate_update(context, exten, ¬ify_data, p, FALSE); ++ AST_LIST_TRAVERSE(&peer->subscriptions, subscription, entry) { ++ struct sip_request req; ++ struct ao2_container *device_state_info = NULL; ++ struct state_notify_data notify_data = { 0, }; ++ char *subtype = NULL, *message = NULL; ++ ++ if (subscription->pvt) { ++ /* Peer hasn't changed, keep original pvt */ ++ if (!ast_sockaddr_cmp(&peer->addr, &subscription->pvt->sa)) { ++ continue; ++ } ++ ++ dialog_unlink_all(subscription->pvt); ++ dialog_unref(subscription->pvt, "drop pvt"); ++ subscription->pvt = NULL; ++ } ++ ++ if (!subscription->pvt) { ++ if (!(subscription->pvt = sip_alloc(NULL, NULL, 0, SIP_NOTIFY, NULL, 0))) { ++ return; ++ } ++ } ++ ++ /* Don't use create_addr_from_peer here as it may fail due to the peer not having responded to an OPTIONS request yet */ ++ if (!ast_strlen_zero(peer->tohost)) { ++ ast_string_field_set(subscription->pvt, tohost, peer->tohost); ++ } else { ++ ast_string_field_set(subscription->pvt, tohost, ast_sockaddr_stringify_host_remote(&peer->addr)); ++ } ++ ++ subscription->pvt->portinuri = peer->portinuri; ++ subscription->pvt->fromdomainport = peer->fromdomainport; ++ ++ ast_string_field_set(subscription->pvt, fullcontact, peer->fullcontact); ++ ast_string_field_set(subscription->pvt, username, peer->username); ++ ast_string_field_set(subscription->pvt, fromuser, subscription->exten); ++ ast_string_field_set(subscription->pvt, fromname, ""); ++ ++ ast_string_field_set(subscription->pvt, context, subscription->context); ++ ast_string_field_set(subscription->pvt, exten, subscription->exten); ++ ast_string_field_build(subscription->pvt, subscribeuri, "%s@%s", subscription->exten, subscription->context); ++ ++ ast_copy_flags(&subscription->pvt->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY); ++ ast_copy_flags(&subscription->pvt->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY); ++ ast_copy_flags(&subscription->pvt->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY); ++ ++ /* Notify is outgoing call */ ++ ast_set_flag(&subscription->pvt->flags[0], SIP_OUTGOING); ++ ast_set_flag(&subscription->pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); ++ ast_set_flag(&subscription->pvt->flags[2], SIP_PAGE3_SUBSCRIPTIONSTATE_ACTIVE); ++ ++ subscription->pvt->subscribed = PIDF_XML; /* Needs to be configurable */ ++ subscription->pvt->expiry = 0; ++ ++ subscription->pvt->sa = peer->addr; ++ subscription->pvt->recv = peer->addr; ++ copy_socket_data(&subscription->pvt->socket, &peer->socket); ++ ++ /* Recalculate our side, and recalculate Call ID */ ++ ast_sip_ouraddrfor(&subscription->pvt->sa, &subscription->pvt->ourip, subscription->pvt); ++ change_callid_pvt(subscription->pvt, NULL); ++ ++ initreqprep(&req, subscription->pvt, SIP_NOTIFY, NULL); ++ initialize_initreq(subscription->pvt, &req); ++ ++ /* Because we only use this req to initialize the pvt's initreq we have to manually deallocate it */ ++ deinit_req(&req); ++ ++ subscription->pvt->stateid = ast_extension_state_add_extended(subscription->pvt->context, subscription->pvt->exten, cb_extensionstate, subscription->pvt); ++ ++ if (subscription->pvt->stateid == -1) { ++ dialog_unlink_all(subscription->pvt); ++ dialog_unref(subscription->pvt, "drop pvt"); ++ subscription->pvt = NULL; ++ ++ continue; ++ } ++ ++ ast_debug(1, "Adding subscription for %s@%s (%s)\n", subscription->exten, subscription->context, subscription->pvt->callid); ++ ++ notify_data.state = ast_extension_state_extended(NULL, subscription->context, subscription->exten, &device_state_info); ++ if (notify_data.state < 0) { ++ ao2_cleanup(device_state_info); ++ continue; ++ } ++ ++ notify_data.presence_state = ast_hint_presence_state(NULL, subscription->context, subscription->exten, &subtype, &message); ++ notify_data.presence_subtype = subtype; ++ notify_data.presence_message = message; ++ notify_data.device_state_info = device_state_info; ++ ++ if (notify_data.state & AST_EXTENSION_RINGING) { ++ struct ast_channel *ringing = find_ringing_channel(notify_data.device_state_info, NULL); ++ ++ if (ringing) { ++ subscription->pvt->last_ringing_channel_time = ast_channel_creationtime(ringing); ++ ao2_ref(ringing, -1); ++ } ++ } ++ ++ extensionstate_update(subscription->context, subscription->exten, ¬ify_data, subscription->pvt, TRUE); ++ ++ ao2_cleanup(device_state_info); ++ ast_free(subtype); ++ ast_free(message); ++ } + } + + /*! \brief Send a fake 401 Unauthorized response when the administrator +@@ -17878,7 +18960,7 @@ + char tmp[256]; + char *c, *name, *unused_password, *domain; + char *uri2 = ast_strdupa(uri); +- int send_mwi = 0; ++ int addrchanged, registered = 0; + + terminate_uri(uri2); + +@@ -17948,6 +19030,16 @@ + } + peer = sip_find_peer(name, NULL, TRUE, FINDPEERS, FALSE, 0); + ++ /* Cisco USECALLMANAGER failover */ ++ if (peer && ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ const char *contact = sip_get_header(req, "Contact"); ++ ++ if (strcasestr(contact, ";expires=0;cisco-keep-alive")) { ++ transmit_response_with_date(p, "200 OK", req); ++ return 0; ++ } ++ } ++ + /* If we don't want username disclosure, use the bogus_peer when a user + * is not found. */ + if (!peer && sip_cfg.alwaysauthreject && sip_cfg.autocreatepeer == AUTOPEERS_DISABLED) { +@@ -17987,7 +19079,7 @@ + + /* We have a successful registration attempt with proper authentication, + now, update the peer */ +- switch (parse_register_contact(p, peer, req)) { ++ switch (parse_register_contact(p, peer, req, &addrchanged)) { + case PARSE_REGISTER_DENIED: + ast_log(LOG_WARNING, "Registration denied because of contact ACL\n"); + transmit_response_with_date(p, "603 Denied", req); +@@ -18002,7 +19094,6 @@ + ast_string_field_set(p, fullcontact, peer->fullcontact); + transmit_response_with_date(p, "200 OK", req); + res = 0; +- send_mwi = 1; + break; + case PARSE_REGISTER_UPDATE: + ast_string_field_set(p, fullcontact, peer->fullcontact); +@@ -18010,9 +19101,14 @@ + if (p->expiry != 0) { + update_peer(peer, p->expiry); + } ++ ast_set2_flag(&p->flags[1], ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER), SIP_PAGE2_CISCO_USECALLMANAGER); + /* Say OK and ask subsystem to retransmit msg counter */ +- transmit_response_with_date(p, "200 OK", req); +- send_mwi = 1; ++ if (p->expiry != 0 && ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && addrchanged) { ++ transmit_response_with_optionsind(p, req); ++ } else { ++ transmit_response_with_date(p, "200 OK", req); ++ } ++ registered = 1; + res = 0; + break; + } +@@ -18036,7 +19132,7 @@ + } + ao2_lock(peer); + sip_cancel_destroy(p); +- switch (parse_register_contact(p, peer, req)) { ++ switch (parse_register_contact(p, peer, req, &addrchanged)) { + case PARSE_REGISTER_DENIED: + ast_log(LOG_WARNING, "Registration denied because of contact ACL\n"); + transmit_response_with_date(p, "403 Forbidden", req); +@@ -18050,13 +19146,17 @@ + case PARSE_REGISTER_QUERY: + ast_string_field_set(p, fullcontact, peer->fullcontact); + transmit_response_with_date(p, "200 OK", req); +- send_mwi = 1; + res = 0; + break; + case PARSE_REGISTER_UPDATE: + ast_string_field_set(p, fullcontact, peer->fullcontact); ++ ast_set2_flag(&p->flags[1], ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER), SIP_PAGE2_CISCO_USECALLMANAGER); + /* Say OK and ask subsystem to retransmit msg counter */ +- transmit_response_with_date(p, "200 OK", req); ++ if (p->expiry != 0 && ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && addrchanged) { ++ transmit_response_with_optionsind(p, req); ++ } else { ++ transmit_response_with_date(p, "200 OK", req); ++ } + if (peer->endpoint) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + ast_endpoint_set_state(peer->endpoint, AST_ENDPOINT_ONLINE); +@@ -18065,7 +19165,7 @@ + "address", ast_sockaddr_stringify(addr)); + ast_endpoint_blob_publish(peer->endpoint, ast_endpoint_state_type(), blob); + } +- send_mwi = 1; ++ registered = 1; + res = 0; + break; + } +@@ -18073,10 +19173,19 @@ + } + } + if (!res) { +- if (send_mwi) { +- sip_pvt_unlock(p); +- sip_send_mwi_to_peer(peer, 0); +- sip_pvt_lock(p); ++ if (p->expiry != 0 && registered) { ++ if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ /* We only need to do an update if the peer addr has changed */ ++ if (addrchanged) { ++ register_peer_aliases(peer); ++ sip_send_bulkupdate(peer); ++ extensionstate_subscriptions(peer); ++ } ++ } else { ++ sip_pvt_unlock(p); ++ sip_send_mwi(peer, 0); ++ sip_pvt_lock(p); ++ } + } else { + update_peer_lastmsgssent(peer, -1, 0); + } +@@ -18195,82 +19304,6 @@ + } + } + +-/*! \brief Parse the parts of the P-Asserted-Identity header +- * on an incoming packet. Returns 1 if a valid header is found +- * and it is different from the current caller id. +- */ +-static int get_pai(struct sip_pvt *p, struct sip_request *req) +-{ +- char pai[256]; +- char privacy[64]; +- char *cid_num = NULL; +- char *cid_name = NULL; +- char emptyname[1] = ""; +- int callingpres = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED; +- char *uri = NULL; +- int is_anonymous = 0, do_update = 1, no_name = 0; +- +- ast_copy_string(pai, sip_get_header(req, "P-Asserted-Identity"), sizeof(pai)); +- +- if (ast_strlen_zero(pai)) { +- return 0; +- } +- +- /* use the reqresp_parser function get_name_and_number*/ +- if (get_name_and_number(pai, &cid_name, &cid_num)) { +- return 0; +- } +- +- if (global_shrinkcallerid && ast_is_shrinkable_phonenumber(cid_num)) { +- ast_shrink_phone_number(cid_num); +- } +- +- uri = get_in_brackets(pai); +- if (!strncasecmp(uri, "sip:anonymous@anonymous.invalid", 31)) { +- callingpres = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED; +- /*XXX Assume no change in cid_num. Perhaps it should be +- * blanked? +- */ +- ast_free(cid_num); +- is_anonymous = 1; +- cid_num = (char *)p->cid_num; +- } +- +- ast_copy_string(privacy, sip_get_header(req, "Privacy"), sizeof(privacy)); +- if (!ast_strlen_zero(privacy) && strcasecmp(privacy, "none")) { +- callingpres = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED; +- } +- if (!cid_name) { +- no_name = 1; +- cid_name = (char *)emptyname; +- } +- /* Only return true if the supplied caller id is different */ +- if (!strcasecmp(p->cid_num, cid_num) && !strcasecmp(p->cid_name, cid_name) && p->callingpres == callingpres) { +- do_update = 0; +- } else { +- +- ast_string_field_set(p, cid_num, cid_num); +- ast_string_field_set(p, cid_name, cid_name); +- p->callingpres = callingpres; +- +- if (p->owner) { +- ast_set_callerid(p->owner, cid_num, cid_name, NULL); +- ast_channel_caller(p->owner)->id.name.presentation = callingpres; +- ast_channel_caller(p->owner)->id.number.presentation = callingpres; +- } +- } +- +- /* get_name_and_number allocates memory for cid_num and cid_name so we have to free it */ +- if (!is_anonymous) { +- ast_free(cid_num); +- } +- if (!no_name) { +- ast_free(cid_name); +- } +- +- return do_update; +-} +- + /*! \brief Get name, number and presentation from remote party id header, + * returns true if a valid header was found and it was different from the + * current caller id. +@@ -18285,6 +19318,7 @@ + char *privacy = ""; + char *screen = ""; + char *start, *end; ++ int pai; + + if (!ast_test_flag(&p->flags[0], SIP_TRUSTRPID)) + return 0; +@@ -18293,7 +19327,13 @@ + req = &p->initreq; + ast_copy_string(tmp, sip_get_header(req, "Remote-Party-ID"), sizeof(tmp)); + if (ast_strlen_zero(tmp)) { +- return get_pai(p, req); ++ ast_copy_string(tmp, sip_get_header(req, "P-Asserted-Identity"), sizeof(tmp)); ++ if (ast_strlen_zero(tmp)) { ++ return 0; ++ } ++ pai = 1; ++ } else { ++ pai = 0; + } + + /* +@@ -18345,7 +19385,13 @@ + if (!end) + return 0; + *end++ = '\0'; +- if (*end) { ++ if (pai) { ++ if (!strcasecmp(sip_get_header(req, "Privacy"), "id")) { ++ callingpres = AST_PRES_PROHIB_USER_NUMBER_PASSED_SCREEN; ++ } else if (!strcasecmp(cid_num, "anonymous@anonymous.invalid")) { ++ callingpres = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED; ++ } ++ } else if (*end) { + start = end; + if (*start != ';') + return 0; +@@ -18685,6 +19731,14 @@ + return SIP_GET_DEST_EXTEN_NOT_FOUND; + } + ++/*! \brief Find a companion dialog */ ++struct sip_pvt *get_sip_pvt(const char *callid, const char *tag, const char *theirtag) ++{ ++ struct sip_pvt dialog = { .callid = callid, .tag = tag, .theirtag = theirtag }; ++ ++ return ao2_find(dialogs, &dialog, OBJ_POINTER); ++} ++ + /*! \brief Find a companion dialog based on Replaces information + * + * This information may come from a Refer-To header in a REFER or from +@@ -19267,10 +20321,10 @@ + + /* Then find devices based on IP */ + if (!peer) { +- char *uri_tmp, *callback = NULL, *dummy; ++ char *uri_tmp, *callbackexten = NULL, *dummy; + uri_tmp = ast_strdupa(uri2); +- parse_uri(uri_tmp, "sip:,sips:,tel:", &callback, &dummy, &dummy, &dummy); +- if (!ast_strlen_zero(callback) && (peer = sip_find_peer_by_ip_and_exten(&p->recv, callback, p->socket.type))) { ++ parse_uri(uri_tmp, "sip:,sips:,tel:", &callbackexten, &dummy, &dummy, &dummy); ++ if (!ast_strlen_zero(callbackexten) && (peer = sip_find_peer_by_ip_and_exten(&p->recv, callbackexten, p->socket.type))) { + ; /* found, fall through */ + } else { + peer = sip_find_peer(NULL, &p->recv, TRUE, FINDPEERS, FALSE, p->socket.type); +@@ -19338,6 +20392,7 @@ + + do_setnat(p); + ++ ast_string_field_set(p, authname, peer->name); + ast_string_field_set(p, peersecret, peer->secret); + ast_string_field_set(p, peermd5secret, peer->md5secret); + ast_string_field_set(p, subscribecontext, peer->subscribecontext); +@@ -19365,12 +20420,17 @@ + + p->allowtransfer = peer->allowtransfer; + ++ /* Cisco peers only auth using the credentials of the primary peer */ ++ if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && peer->cisco_lineindex > 1) { ++ ast_string_field_set(p, authname, peer->cisco_authname); ++ } ++ + if (ast_test_flag(&peer->flags[0], SIP_INSECURE_INVITE)) { + /* Pretend there is no required authentication */ + ast_string_field_set(p, peersecret, NULL); + ast_string_field_set(p, peermd5secret, NULL); + } +- if (!(res = check_auth(p, req, peer->name, p->peersecret, p->peermd5secret, sipmethod, uri2, reliable))) { ++ if (!(res = check_auth(p, req, p->authname, p->peersecret, p->peermd5secret, sipmethod, uri2, reliable))) { + + /* build_peer, called through sip_find_peer, is not able to check the + * sip_pvt->natdetected flag in order to determine if the peer is behind +@@ -19738,7 +20798,7 @@ + return; + } + +- if (!(buf = get_content(req))) { ++ if (!(buf = get_content(req, 0, req->lines))) { + ast_log(LOG_WARNING, "Unable to retrieve text from %s\n", p->callid); + transmit_response(p, "500 Internal Server Error", req); + if (!p->owner) { +@@ -21168,7 +22228,7 @@ + } + + /*! \brief list peer mailboxes to CLI */ +-static void peer_mailboxes_to_str(struct ast_str **mailbox_str, struct sip_peer *peer) ++static void get_peer_mailboxes(struct ast_str **mailbox_str, struct sip_peer *peer) + { + struct sip_mailbox *mailbox; + +@@ -21201,6 +22261,8 @@ + struct ast_variable *v; + int x = 0, load_realtime; + int realtimepeers; ++ struct sip_alias *alias; ++ struct sip_subscription *subscription; + + realtimepeers = ast_check_realtime("sippeers"); + +@@ -21281,7 +22343,7 @@ + print_named_groups(fd, peer->named_callgroups, 0); + ast_cli(fd, " Nam. Pickupgr: "); + print_named_groups(fd, peer->named_pickupgroups, 0); +- peer_mailboxes_to_str(&mailbox_str, peer); ++ get_peer_mailboxes(&mailbox_str, peer); + ast_cli(fd, " MOH Suggest : %s\n", peer->mohsuggest); + ast_cli(fd, " Mailbox : %s\n", ast_str_buffer(mailbox_str)); + ast_cli(fd, " VM Extension : %s\n", peer->vmexten); +@@ -21360,9 +22422,9 @@ + ast_cli(fd, " Qualify Freq : %d ms\n", peer->qualifyfreq); + ast_cli(fd, " Keepalive : %d ms\n", peer->keepalive * 1000); + if (peer->chanvars) { +- ast_cli(fd, " Variables :\n"); ++ ast_cli(fd, " Variables : "); + for (v = peer->chanvars ; v ; v = v->next) +- ast_cli(fd, " %s = %s\n", v->name, v->value); ++ ast_cli(fd, "%s%s = %s\n", v != peer->chanvars ? " " : "", v->name, v->value); + } + + ast_cli(fd, " Sess-Timers : %s\n", stmode2str(peer->stimer.st_mode_oper)); +@@ -21374,6 +22436,28 @@ + ast_cli(fd, " Use Reason : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_Q850_REASON))); + ast_cli(fd, " Encryption : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_USE_SRTP))); + ast_cli(fd, " RTCP Mux : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[2], SIP_PAGE3_RTCP_MUX))); ++ ast_cli(fd, " DND : %s\n", AST_CLI_YESNO(peer->donotdisturb)); ++ ast_cli(fd, " CallFwd Ext. : %s\n", peer->callforward); ++ ast_cli(fd, " Hunt Group : %s\n", AST_CLI_YESNO(peer->huntgroup)); ++ if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ ast_cli(fd, " Device Name : %s\n", peer->cisco_devicename); ++ ast_cli(fd, " Active Load : %s\n", peer->cisco_activeload); ++ ast_cli(fd, " Inactive Load: %s\n", peer->cisco_inactiveload); ++ ++ if (!AST_LIST_EMPTY(&peer->aliases)) { ++ ast_cli(fd, " BulkReg.Peers: "); ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ ast_cli(fd, "%s%s (Line %d)\n", alias != AST_LIST_FIRST(&peer->aliases) ? " " : "", alias->name, alias->lineindex); ++ } ++ } ++ ++ if (!AST_LIST_EMPTY(&peer->subscriptions)) { ++ ast_cli(fd, " Subscriptions: "); ++ AST_LIST_TRAVERSE(&peer->subscriptions, subscription, entry) { ++ ast_cli(fd, "%s%s@%s\n", subscription != AST_LIST_FIRST(&peer->subscriptions) ? " " : "", subscription->exten, subscription->context); ++ } ++ } ++ } + ast_cli(fd, "\n"); + peer = sip_unref_peer(peer, "sip_show_peer: sip_unref_peer: done with peer ptr"); + } else if (peer && type == 1) { /* manager listing */ +@@ -21410,7 +22494,7 @@ + astman_append(s, "%s\r\n", ast_print_namedgroups(&tmp_str, peer->named_pickupgroups)); + ast_str_reset(tmp_str); + astman_append(s, "MOHSuggest: %s\r\n", peer->mohsuggest); +- peer_mailboxes_to_str(&tmp_str, peer); ++ get_peer_mailboxes(&tmp_str, peer); + astman_append(s, "VoiceMailbox: %s\r\n", ast_str_buffer(tmp_str)); + astman_append(s, "TransferMode: %s\r\n", transfermode2str(peer->allowtransfer)); + astman_append(s, "LastMsgsSent: %d\r\n", peer->lastmsgssent); +@@ -21469,7 +22553,19 @@ + } + astman_append(s, "SIP-Use-Reason-Header: %s\r\n", (ast_test_flag(&peer->flags[1], SIP_PAGE2_Q850_REASON)) ? "Y" : "N"); + astman_append(s, "Description: %s\r\n", peer->description); +- ++ astman_append(s, "DoNotDisturb: %s\r\n", peer->donotdisturb ? "Y" : "N"); ++ astman_append(s, "CallForward: %s\r\n", peer->callforward); ++ astman_append(s, "HuntGroup: %s\r\n", peer->huntgroup ? "Y" : "N"); ++ astman_append(s, "CiscoDeviceName: %s\r\n", peer->cisco_devicename); ++ astman_append(s, "CiscoActiveLoad: %s\r\n", peer->cisco_activeload); ++ astman_append(s, "CiscoInactiveLoad: %s\r\n", peer->cisco_inactiveload); ++ astman_append(s, "CiscoLineIndex: %d\r\n", peer->cisco_lineindex); ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ astman_append(s, "Register: %s\r\n", alias->name); ++ } ++ AST_LIST_TRAVERSE(&peer->subscriptions, subscription, entry) { ++ astman_append(s, "Subscribe: %s@%s\r\n", subscription->exten, subscription->context); ++ } + peer = sip_unref_peer(peer, "sip_show_peer: sip_unref_peer: done with peer"); + + } else { +@@ -21584,9 +22680,9 @@ + + ast_cli(a->fd, " Auto-Framing: %s \n", AST_CLI_YESNO(user->autoframing)); + if (user->chanvars) { +- ast_cli(a->fd, " Variables :\n"); ++ ast_cli(a->fd, " Variables : "); + for (v = user->chanvars ; v ; v = v->next) +- ast_cli(a->fd, " %s = %s\n", v->name, v->value); ++ ast_cli(a->fd, "%s%s = %s\n", v != user->chanvars ? " " : "", v->name, v->value); + } + + ast_cli(a->fd, "\n"); +@@ -22194,7 +23290,7 @@ + if (cur->subscribed != NONE && arg->subscriptions) { + struct ast_str *mailbox_str = ast_str_alloca(512); + if (cur->subscribed == MWI_NOTIFICATION && cur->relatedpeer) +- peer_mailboxes_to_str(&mailbox_str, cur->relatedpeer); ++ get_peer_mailboxes(&mailbox_str, cur->relatedpeer); + ast_cli(arg->fd, FORMAT4, ast_sockaddr_stringify_addr(dst), + S_OR(cur->username, S_OR(cur->cid_num, "(None)")), + cur->callid, +@@ -22680,7 +23776,7 @@ + } + } else { + /* Type is application/dtmf, simply use what's in the message body */ +- buf = get_content(req); ++ buf = get_content(req, 0, req->lines); + } + + /* An empty message body requires us to send a 200 OK */ +@@ -22908,6 +24004,8 @@ + static char *sip_cli_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) + { + struct ast_variable *varlist; ++ struct ast_channel *chan; ++ struct ast_str *str; + int i; + + switch (cmd) { +@@ -22937,10 +24035,21 @@ + return CLI_FAILURE; + } + ++ if (!(chan = ast_dummy_channel_alloc())) { ++ ast_cli(a->fd, "Cannot allocate the channel for variables substitution\n"); ++ return CLI_FAILURE; ++ } ++ ++ if (!(str = ast_str_create(32))) { ++ ast_channel_release(chan); ++ ast_cli(a->fd, "Cannot allocate the string for variables substitution\n"); ++ return CLI_FAILURE; ++ } ++ + for (i = 3; i < a->argc; i++) { + struct sip_pvt *p; + char buf[512]; +- struct ast_variable *header, *var; ++ struct ast_variable *var, *header, *headers = NULL; + + if (!(p = sip_alloc(NULL, NULL, 0, SIP_NOTIFY, NULL, 0))) { + ast_log(LOG_WARNING, "Unable to build sip pvt data for notify (memory/socket error)\n"); +@@ -22959,36 +24068,220 @@ + /* Notify is outgoing call */ + ast_set_flag(&p->flags[0], SIP_OUTGOING); + sip_notify_alloc(p); +- p->notify->headers = header = ast_variable_new("Subscription-State", "terminated", ""); ++ ++ /* Recalculate our side, and recalculate Call ID */ ++ ast_sip_ouraddrfor(&p->sa, &p->ourip, p); ++ change_callid_pvt(p, NULL); ++ ++ /* Set the name of the peer being sent the notification so it can be used in ${} functions */ ++ pbx_builtin_setvar_helper(chan, "PEERNAME", p->peername); + + for (var = varlist; var; var = var->next) { + ast_copy_string(buf, var->value, sizeof(buf)); + ast_unescape_semicolon(buf); ++ ast_str_substitute_variables(&str, 0, chan, buf); + + if (!strcasecmp(var->name, "Content")) { + if (ast_str_strlen(p->notify->content)) + ast_str_append(&p->notify->content, 0, "\r\n"); +- ast_str_append(&p->notify->content, 0, "%s", buf); ++ ast_str_append(&p->notify->content, 0, "%s", ast_str_buffer(str)); + } else if (!strcasecmp(var->name, "Content-Length")) { + ast_log(LOG_WARNING, "it is not necessary to specify Content-Length in sip_notify.conf, ignoring\n"); + } else { +- header->next = ast_variable_new(var->name, buf, ""); +- header = header->next; ++ header = ast_variable_new(var->name, ast_str_buffer(str), ""); ++ if (headers) { ++ headers->next = header; ++ } else { ++ p->notify->headers = header; ++ } ++ headers = header; + } + } + +- /* Now that we have the peer's address, set our ip and change callid */ +- ast_sip_ouraddrfor(&p->sa, &p->ourip, p); +- build_via(p); +- +- change_callid_pvt(p, NULL); +- + ast_cli(a->fd, "Sending NOTIFY of type '%s' to '%s'\n", a->argv[2], a->argv[i]); + sip_scheddestroy(p, SIP_TRANS_TIMEOUT); + transmit_invite(p, SIP_NOTIFY, 0, 2, NULL); + dialog_unref(p, "bump down the count of p since we're done with it."); + } + ++ ast_channel_release(chan); ++ ast_free(str); ++ ++ return CLI_SUCCESS; ++} ++ ++/*! \brief Enable/Disable DoNotDisturb on a peer */ ++static char *sip_cli_donotdisturb(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) ++{ ++ struct sip_peer *peer; ++ struct sip_alias *alias; ++ int donotdisturb; ++ ++ switch (cmd) { ++ case CLI_INIT: ++ e->command = "sip donotdisturb {on|off}"; ++ e->usage = ++ "Usage: sip donotdisturb {on|off} \n" ++ " Enables/Disables do not disturb on a SIP peer\n"; ++ return NULL; ++ case CLI_GENERATE: ++ if (a->pos == 3) { ++ return complete_sip_peer(a->word, a->n, 0); ++ } ++ return NULL; ++ } ++ ++ if (a->argc < 4) { ++ return CLI_SHOWUSAGE; ++ } ++ ++ if (!strcasecmp(a->argv[2], "on")) { ++ donotdisturb = 1; ++ } else if (!strcasecmp(a->argv[2], "off")) { ++ donotdisturb = 0; ++ } else { ++ return CLI_SHOWUSAGE; ++ } ++ ++ if (!(peer = sip_find_peer(a->argv[3], NULL, TRUE, FINDPEERS, FALSE, 0))) { ++ ast_cli(a->fd, "No such peer '%s'\n", a->argv[3]); ++ return CLI_FAILURE; ++ } ++ ++ ast_cli(a->fd, "Do Not Disturb on '%s' %s\n", peer->name, donotdisturb ? "enabled" : "disabled"); ++ peer->donotdisturb = donotdisturb; ++ ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", peer->name); ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (alias->peer) { ++ alias->peer->donotdisturb = peer->donotdisturb; ++ ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", alias->peer->name); ++ } ++ } ++ if (!peer->is_realtime) { ++ ast_db_put("SIP/DoNotDisturb", peer->name, donotdisturb ? "yes" : "no"); ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "donotdisturb", donotdisturb ? "yes" : "no", SENTINEL); ++ } ++ sip_send_donotdisturb(peer); ++ peer = sip_unref_peer(peer, "unref after sip_find_peer"); ++ ++ return CLI_SUCCESS; ++} ++ ++/*! \brief Login to/Logout from huntgroup for a peer */ ++static char *sip_cli_huntgroup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) ++{ ++ struct sip_peer *peer; ++ struct sip_alias *alias; ++ int huntgroup; ++ ++ switch (cmd) { ++ case CLI_INIT: ++ e->command = "sip huntgroup {on|off}"; ++ e->usage = ++ "Usage: sip huntgroup {on|off} \n" ++ " Login to/Logout from huntgroup for a SIP peer\n"; ++ return NULL; ++ case CLI_GENERATE: ++ if (a->pos == 3) { ++ return complete_sip_peer(a->word, a->n, 0); ++ } ++ return NULL; ++ } ++ ++ if (a->argc < 4) { ++ return CLI_SHOWUSAGE; ++ } ++ ++ if (!strcasecmp(a->argv[2], "on")) { ++ huntgroup = 1; ++ } else if (!strcasecmp(a->argv[2], "off")) { ++ huntgroup = 0; ++ } else { ++ return CLI_SHOWUSAGE; ++ } ++ ++ if (!(peer = sip_find_peer(a->argv[3], NULL, TRUE, FINDPEERS, FALSE, 0))) { ++ ast_cli(a->fd, "No such peer '%s'\n", a->argv[3]); ++ return CLI_FAILURE; ++ } ++ ++ ast_cli(a->fd, "Hunt Group %s for '%s'\n", huntgroup ? "login" : "logout", peer->name); ++ peer->huntgroup = huntgroup; ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (alias->peer) { ++ alias->peer->huntgroup = peer->huntgroup; ++ } ++ } ++ if (!peer->is_realtime) { ++ ast_db_put("SIP/HuntGroup", peer->name, huntgroup ? "yes" : "no"); ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "huntgroup", huntgroup ? "yes" : "no", SENTINEL); ++ } ++ sip_send_huntgroup(peer); ++ peer = sip_unref_peer(peer, "unref after sip_find_peer"); ++ ++ return CLI_SUCCESS; ++} ++ ++/*! \brief Sets/Removes the call fowarding extension for a peer */ ++static char *sip_cli_callforward(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) ++{ ++ struct sip_peer *peer; ++ const char *callforward; ++ ++ switch (cmd) { ++ case CLI_INIT: ++ e->command = "sip callforward {on|off}"; ++ e->usage = ++ "Usage: sip callforward {on |off }\n" ++ " Sets/Clears the call forwarding extension for a SIP peer\n"; ++ return NULL; ++ case CLI_GENERATE: ++ if (a->pos == 3) { ++ return complete_sip_peer(a->word, a->n, 0); ++ } ++ return NULL; ++ } ++ ++ if (a->argc < 4) { ++ return CLI_SHOWUSAGE; ++ } ++ ++ if (!strcasecmp(a->argv[2], "on")) { ++ if (a->argc < 5) { ++ return CLI_SHOWUSAGE; ++ } ++ callforward = a->argv[4]; ++ } else if (!strcasecmp(a->argv[2], "off")) { ++ callforward = ""; ++ } else { ++ return CLI_SHOWUSAGE; ++ } ++ ++ if (!(peer = sip_find_peer(a->argv[3], NULL, TRUE, FINDPEERS, FALSE, 0))) { ++ ast_cli(a->fd, "No such peer '%s'\n", a->argv[3]); ++ return CLI_FAILURE; ++ } ++ ++ if (ast_strlen_zero(callforward)) { ++ ast_cli(a->fd, "Call forwarding on '%s' cleared\n", peer->name); ++ } else { ++ ast_cli(a->fd, "Call forwarding on '%s' set to %s\n", peer->name, callforward); ++ } ++ ast_string_field_set(peer, callforward, callforward); ++ if (!peer->is_realtime) { ++ if (ast_strlen_zero(peer->callforward)) { ++ ast_db_del("SIP/CallForward", peer->name); ++ } else { ++ ast_db_put("SIP/CallForward", peer->name, callforward); ++ } ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "callforward", peer->callforward, SENTINEL); ++ } ++ sip_send_callforward(peer); ++ peer = sip_unref_peer(peer, "unref after sip_find_peer"); ++ + return CLI_SUCCESS; + } + +@@ -23426,7 +24719,7 @@ + }; + + /*! \brief ${SIPPEER()} Dialplan function - reads peer data */ +-static int function_sippeer(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) ++static int function_sippeer_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) + { + struct sip_peer *peer; + char *colname; +@@ -23480,7 +24773,7 @@ + ast_copy_string(buf, peer->useragent, len); + } else if (!strcasecmp(colname, "mailbox")) { + struct ast_str *mailbox_str = ast_str_alloca(512); +- peer_mailboxes_to_str(&mailbox_str, peer); ++ get_peer_mailboxes(&mailbox_str, peer); + ast_copy_string(buf, ast_str_buffer(mailbox_str), len); + } else if (!strcasecmp(colname, "context")) { + ast_copy_string(buf, peer->context, len); +@@ -23521,11 +24814,88 @@ + } else { + buf[0] = '\0'; + } ++ } else if (!strncasecmp(colname, "vmexten", 7)) { ++ ast_copy_string(buf, peer->vmexten, len); ++ } else if (!strncasecmp(colname, "donotdisturb", 12)) { ++ ast_copy_string(buf, peer->donotdisturb ? "yes" : "no", len); ++ } else if (!strncasecmp(colname, "callforward", 11)) { ++ ast_copy_string(buf, peer->callforward, len); ++ } else if (!strncasecmp(colname, "huntgroup", 9)) { ++ ast_copy_string(buf, peer->huntgroup ? "yes" : "no", len); ++ } else if (!strncasecmp(colname, "regcallid", 9)) { ++ ast_copy_string(buf, peer->regcallid, len); ++ } else if (!strncasecmp(colname, "ciscodevicename", 15)) { ++ ast_copy_string(buf, peer->cisco_devicename, len); ++ } else if (!strncasecmp(colname, "ciscolineindex", 14)) { ++ snprintf(buf, len, "%d", peer->cisco_lineindex); + } else { + buf[0] = '\0'; + } + +- sip_unref_peer(peer, "sip_unref_peer from function_sippeer, just before return"); ++ sip_unref_peer(peer, "sip_unref_peer from function_sippeer_read, just before return"); ++ ++ return 0; ++} ++ ++static int function_sippeer_write(struct ast_channel *chan, const char *cmd, char *data, const char *value) ++{ ++ struct sip_peer *peer; ++ struct sip_alias *alias; ++ char *colname; ++ ++ if ((colname = strchr(data, ','))) { ++ *colname++ = '\0'; ++ } else { ++ colname = ""; ++ } ++ ++ if (!(peer = sip_find_peer(data, NULL, TRUE, FINDPEERS, FALSE, 0))) { ++ return -1; ++ } ++ ++ if (!strncasecmp(colname, "donotdisturb", 12)) { ++ peer->donotdisturb = ast_true(value); ++ ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", peer->name); ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (alias->peer) { ++ alias->peer->donotdisturb = peer->donotdisturb; ++ ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", alias->peer->name); ++ } ++ } ++ if (!peer->is_realtime) { ++ ast_db_put("SIP/DoNotDisturb", peer->name, peer->donotdisturb ? "yes" : "no"); ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "donotdisturb", peer->donotdisturb ? "yes" : "no", SENTINEL); ++ } ++ sip_send_donotdisturb(peer); ++ } else if (!strncasecmp(colname, "huntgroup", 9)) { ++ peer->huntgroup = ast_true(value); ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (alias->peer) { ++ alias->peer->huntgroup = peer->huntgroup; ++ } ++ } ++ if (!peer->is_realtime) { ++ ast_db_put("SIP/HuntGroup", peer->name, peer->huntgroup ? "yes" : "no"); ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "huntgroup", peer->huntgroup ? "yes" : "no", SENTINEL); ++ } ++ sip_send_huntgroup(peer); ++ } else if (!strncasecmp(colname, "callforward", 11)) { ++ ast_string_field_set(peer, callforward, value); ++ if (!peer->is_realtime) { ++ if (ast_strlen_zero(peer->callforward)) { ++ ast_db_del("SIP/CallForward", peer->name); ++ } else { ++ ast_db_put("SIP/CallForward", peer->name, peer->callforward); ++ } ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "callforward", peer->callforward, SENTINEL); ++ } ++ sip_send_callforward(peer); ++ } ++ ++ sip_unref_peer(peer, "sip_unref_peer from function_sippeer_write, just before return"); + + return 0; + } +@@ -23533,7 +24903,8 @@ + /*! \brief Structure to declare a dialplan function: SIPPEER */ + static struct ast_custom_function sippeer_function = { + .name = "SIPPEER", +- .read = function_sippeer, ++ .read = function_sippeer_read, ++ .write = function_sippeer_write + }; + + /*! \brief update redirecting information for a channel based on headers +@@ -24133,6 +25504,35 @@ + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_NOT_CACHABLE, "SIP/%s", p->relatedpeer->name); + } + } ++ if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ const char *autoanswer = pbx_builtin_getvar_helper(p->owner, "CISCO_AUTOANSWER"); ++ ++ if (ast_true(autoanswer)) { ++ struct sip_pvt *ansp; ++ ++ if ((ansp = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ struct ast_str *content = ast_str_alloca(8192); ++ ++ copy_pvt_data(ansp, p); ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", p->callid); ++ ast_str_append(&content, 0, "%s\n", p->theirtag); ++ ast_str_append(&content, 0, "%s\n", p->tag); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ sip_pvt_lock(ansp); ++ transmit_refer_with_content(ansp, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); ++ sip_pvt_unlock(ansp); ++ dialog_unref(ansp, "bump down the count of pvt since we're done with it."); ++ } ++ } ++ } + } + if (find_sdp(req)) { + if (p->invitestate != INV_CANCELLED) { +@@ -24393,6 +25793,20 @@ + ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); + xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, TRUE); + sched_check_pendings(p); ++ ++ if (ast_test_flag(&p->flags[2], SIP_PAGE3_RELAY_NEAREND) || ast_test_flag(&p->flags[2], SIP_PAGE3_RELAY_FAREND)) { ++ if (ast_test_flag(&p->flags[2], SIP_PAGE3_RELAY_NEAREND)) { ++ ast_clear_flag(&p->flags[2], SIP_PAGE3_RELAY_NEAREND); ++ start_record_thread(p->join_callid, p->join_tag, p->join_theirtag, 0); ++ } else { ++ ast_clear_flag(&p->flags[2], SIP_PAGE3_RELAY_FAREND); ++ } ++ ast_set_flag(&p->flags[2], SIP_PAGE3_SDP_ACK); ++ transmit_invite(p, SIP_INVITE, FALSE, 1, NULL); ++ } else if (ast_test_flag(&p->flags[2], SIP_PAGE3_SDP_ACK)) { ++ ast_clear_flag(&p->flags[2], SIP_PAGE3_SDP_ACK); ++ ast_set_flag(&p->flags[2], SIP_PAGE3_CISCO_RECORDING); ++ } + break; + + case 407: /* Proxy authentication */ +@@ -24725,6 +26139,7 @@ + return; + + switch (resp) { ++ case 200: /* Out of Dialog REFER */ + case 202: /* Transfer accepted */ + /* We need to do something here */ + /* The transferee is now sending INVITE to target */ +@@ -24976,6 +26391,8 @@ + struct sip_peer *peer = /* sip_ref_peer( */ p->relatedpeer /* , "bump refcount on p, as it is being used in this function(handle_response_peerpoke)")*/ ; /* hope this is already refcounted! */ + int statechanged, is_reachable, was_reachable; + int pingtime = ast_tvdiff_ms(ast_tvnow(), peer->ps); ++ const char *status; ++ char lastms[20]; + + /* + * Compute the response time to a ping (goes in peer->lastms.) +@@ -24999,26 +26416,24 @@ + is_reachable = pingtime <= peer->maxms; + statechanged = peer->lastms == 0 /* yes, unknown before */ + || was_reachable != is_reachable; ++ status = is_reachable ? "Reachable" : "Lagged"; ++ snprintf(lastms, sizeof(lastms), "%d", pingtime); + + peer->lastms = pingtime; + peer->call = dialog_unref(peer->call, "unref dialog peer->call"); +- if (statechanged) { +- const char *s = is_reachable ? "Reachable" : "Lagged"; +- char str_lastms[20]; +- +- snprintf(str_lastms, sizeof(str_lastms), "%d", pingtime); + ++ if (statechanged) { + ast_log(LOG_NOTICE, "Peer '%s' is now %s. (%dms / %dms)\n", +- peer->name, s, pingtime, peer->maxms); ++ peer->name, status, pingtime, peer->maxms); + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); + if (sip_cfg.peer_rtupdate) { +- ast_update_realtime(ast_check_realtime("sipregs") ? "sipregs" : "sippeers", "name", peer->name, "lastms", str_lastms, SENTINEL); ++ ast_update_realtime(ast_check_realtime("sipregs") ? "sipregs" : "sippeers", "name", peer->name, "lastms", lastms, SENTINEL); + } + if (peer->endpoint) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + ast_endpoint_set_state(peer->endpoint, AST_ENDPOINT_ONLINE); + blob = ast_json_pack("{s: s, s: i}", +- "peer_status", s, ++ "peer_status", status, + "time", pingtime); + ast_endpoint_blob_publish(peer->endpoint, ast_endpoint_state_type(), blob); + } +@@ -25028,6 +26443,38 @@ + } + } + ++ if (!AST_LIST_EMPTY(&peer->aliases)) { ++ struct sip_alias *alias; ++ ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (!alias->peer) { ++ continue; ++ } ++ alias->peer->lastms = pingtime; ++ ++ if (statechanged) { ++ ast_log(LOG_NOTICE, "Peer '%s' is now %s. (%dms / %dms)\n", ++ alias->peer->name, status, pingtime, alias->peer->maxms); ++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); ++ if (sip_cfg.peer_rtupdate) { ++ ast_update_realtime("sippeers", "name", alias->peer->name, "lastms", lastms, SENTINEL); ++ } ++ if (alias->peer->endpoint) { ++ RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); ++ ast_endpoint_set_state(alias->peer->endpoint, AST_ENDPOINT_ONLINE); ++ blob = ast_json_pack("{s: s, s: i}", ++ "peer_status", status, ++ "time", pingtime); ++ ast_endpoint_blob_publish(alias->peer->endpoint, ast_endpoint_state_type(), blob); ++ } ++ ++ if (is_reachable && sip_cfg.regextenonqualify) { ++ register_peer_exten(alias->peer, TRUE); ++ } ++ } ++ } ++ } ++ + pvt_set_needdestroy(p, "got OPTIONS response"); + + /* Try again eventually */ +@@ -25560,6 +27007,9 @@ + + /* Wait for 487, then destroy */ + } else if (sipmethod == SIP_BYE) { ++ if (ast_test_flag(&p->flags[2], SIP_PAGE3_RTP_STATS_ON_BYE)) { ++ parse_rtp_stats(p, req); ++ } + pvt_set_needdestroy(p, "transaction completed"); + } + break; +@@ -25658,6 +27108,2239 @@ + return 0; + } + ++/*! \brief Handle idivert request */ ++static int handle_remotecc_idivert(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_pvt *targetcall_pvt; ++ struct ast_channel *chan, *bridged; ++ ++ /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ ++ if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { ++ ast_debug(1, "dialogid call leg does not exist\n"); ++ return -1; ++ } ++ ++ sip_pvt_lock(targetcall_pvt); ++ ++ if (!(chan = targetcall_pvt->owner)) { ++ ast_debug(1, "no owner channel\n"); ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ return -1; ++ } ++ ++ ast_channel_ref(chan); ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ if (ast_channel_state(chan) == AST_STATE_RINGING) { ++ ast_queue_control(chan, AST_CONTROL_BUSY); ++ } else if (ast_channel_state(chan) == AST_STATE_UP) { ++ if ((bridged = ast_channel_bridge_peer(chan))) { ++ pbx_builtin_setvar_helper(bridged, "IDIVERT_PEERNAME", peer->name); ++ ast_async_goto(bridged, peer->context, "idivert", 1); ++ ast_channel_unref(bridged); ++ } ++ } ++ ++ ast_channel_unref(chan); ++ ++ return 0; ++} ++ ++/*! \brief Handle hlog request */ ++static int handle_remotecc_hlog(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_alias *alias; ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ peer->huntgroup = !peer->huntgroup; ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (alias->peer) { ++ alias->peer->huntgroup = peer->huntgroup; ++ } ++ } ++ if (!peer->is_realtime) { ++ ast_db_put("SIP/HuntGroup", peer->name, peer->huntgroup ? "yes" : "no"); ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "huntgroup", peer->huntgroup ? "yes" : "no", SENTINEL); ++ } ++ sip_send_huntgroup(peer); ++ ++ return 0; ++} ++ ++/*! \brief destroy conference callback for ao2_alloc */ ++static void destroy_conference(void *obj) ++{ ++ struct sip_conference *conference = obj; ++ ++ ast_verb(3, "Destroying ad-hoc conference %d\n", conference->confid); ++ ++ if (conference->bridge) { ++ ast_bridge_destroy(conference->bridge, 0); ++ conference->bridge = NULL; ++ } ++ ++ AST_LIST_LOCK(&conferences); ++ AST_LIST_REMOVE(&conferences, conference, entry); ++ AST_LIST_UNLOCK(&conferences); ++} ++ ++/*! \brief create conference and assign it to a sip_pvt */ ++static int create_conference(struct sip_pvt *pvt) ++{ ++ struct sip_conference *conference; ++ ++ if (!(conference = ao2_alloc(sizeof(*conference), destroy_conference))) { ++ return -1; ++ } ++ ++ if (!(conference->bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_MULTIMIX, AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY, "SIP", "Conference", NULL))) { ++ ao2_ref(conference, -1); ++ return -1; ++ } ++ ++ ast_bridge_set_internal_sample_rate(conference->bridge, 8000); ++ ast_bridge_set_mixing_interval(conference->bridge, 20); ++ ++ ast_bridge_set_talker_src_video_mode(conference->bridge); ++ ++ conference->confid = ++next_confid; ++ conference->keep = ast_test_flag(&pvt->flags[2], SIP_PAGE3_CISCO_KEEP_CONFERENCE) ? 1 : 0; ++ conference->multiadmin = ast_test_flag(&pvt->flags[2], SIP_PAGE3_CISCO_MULTIADMIN_CONFERENCE) ? 1 : 0; ++ AST_LIST_HEAD_INIT_NOLOCK(&conference->participants); ++ ++ pvt->conference = conference; ++ ++ AST_LIST_LOCK(&conferences); ++ AST_LIST_INSERT_TAIL(&conferences, conference, entry); ++ AST_LIST_UNLOCK(&conferences); ++ ++ ast_verb(3, "Creating ad-hoc conference %d\n", conference->confid); ++ ++ return 0; ++} ++ ++static int talk_detector(struct ast_bridge_channel *chan, void *hook_pvt, int talking) ++{ ++ struct sip_participant *participant = hook_pvt; ++ struct sip_conference *conference = participant->conference; ++ ++ ast_debug(1, "%s %s talking in ad-hoc conference %d\n", ast_channel_name(participant->chan), talking ? "started" : "stopped", conference->confid); ++ participant->talking = talking; ++ ++ return 0; ++} ++ ++/*! \brief cleanup participant structure after leaving bridge */ ++static int leave_conference(struct ast_bridge_channel *chan, void *hook_pvt) ++{ ++ struct sip_participant *participant = hook_pvt; ++ struct sip_conference *conference = participant->conference; ++ ++ ast_verb(3, "%s left ad-hoc conference %d\n", ast_channel_name(participant->chan), conference->confid); ++ ++ ao2_lock(conference); ++ AST_LIST_REMOVE(&conference->participants, participant, entry); ++ if (participant->administrator) { ++ conference->administrators--; ++ } else { ++ conference->users--; ++ } ++ ao2_unlock(conference); ++ ++ if (conference->administrators + conference->users > 1) { ++ struct sip_participant *participant; ++ ++ AST_LIST_TRAVERSE(&conference->participants, participant, entry) { ++ ast_bridge_channel_queue_playfile(ast_channel_internal_bridge_channel(participant->chan), NULL, "confbridge-leave", NULL); ++ } ++ } ++ ++ if (conference->administrators + conference->users == 1) { ++ struct sip_participant *participant; ++ ++ ast_verb(3, "Only one participant left in ad-hoc conference %d, removing.\n", conference->confid); ++ ++ participant = AST_LIST_FIRST(&conference->participants); ++ ast_bridge_remove(conference->bridge, participant->chan); ++ } else if (conference->users && !conference->administrators && !conference->keep) { ++ struct sip_participant *participant; ++ ++ ast_verb(3, "No more administrators in ad-hoc conference %d\n", conference->confid); ++ ++ AST_LIST_TRAVERSE(&conference->participants, participant, entry) { ++ ast_bridge_remove(conference->bridge, participant->chan); ++ } ++ } ++ ++ ast_channel_unref(participant->chan); ++ ao2_ref(participant->conference, -1); ++ ast_free(participant); ++ ++ return -1; ++} ++ ++/*! \brief allocate participant structure and move channel into conference bridge */ ++static int join_conference(struct sip_conference *conference, struct ast_channel *chan, int administrator) ++{ ++ struct sip_participant *participant; ++ struct ast_bridge_channel *bridgechan; ++ ++ if (!administrator && conference->multiadmin) { ++ ast_channel_lock(chan); ++ if (IS_SIP_TECH(ast_channel_tech(chan))) { ++ struct sip_pvt *pvt = ast_channel_tech_pvt(chan); ++ ++ sip_pvt_lock(pvt); ++ if (ast_test_flag(&pvt->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && ast_test_flag(&pvt->flags[2], SIP_PAGE3_CISCO_MULTIADMIN_CONFERENCE)) { ++ ao2_ref(conference, +1); ++ pvt->conference = conference; ++ administrator = 1; ++ } ++ sip_pvt_unlock(pvt); ++ } ++ ast_channel_unlock(chan); ++ } ++ ++ if (!(participant = ast_calloc(1, sizeof(*participant)))) { ++ return -1; ++ } ++ ++ ast_channel_ref(chan); ++ participant->chan = chan; ++ ++ ao2_ref(conference, +1); ++ participant->conference = conference; ++ ++ participant->callid = ++conference->next_callid; ++ participant->administrator = administrator; ++ ++ bridgechan = ast_channel_internal_bridge_channel(chan); ++ ao2_ref(bridgechan, +1); ++ ++ bridgechan->inhibit_colp = 1; ++ ++ if (ast_bridge_move(conference->bridge, ast_channel_internal_bridge(chan), chan, NULL, 0)) { ++ ao2_ref(bridgechan, -1); ++ ao2_ref(conference, -1); ++ ast_channel_unref(chan); ++ ast_free(participant); ++ return -1; ++ } ++ ++ ast_bridge_features_remove(bridgechan->features, AST_BRIDGE_HOOK_REMOVE_ON_PULL); ++ ast_bridge_leave_hook(bridgechan->features, leave_conference, participant, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL); ++ ast_bridge_talk_detector_hook(bridgechan->features, talk_detector, participant, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL); ++ ao2_ref(bridgechan, -1); ++ ++ ao2_lock(conference); ++ AST_LIST_INSERT_HEAD(&conference->participants, participant, entry); ++ if (administrator) { ++ conference->administrators++; ++ } else { ++ conference->users++; ++ } ++ ao2_unlock(conference); ++ ++ if (conference->administrators + conference->users > 2) { ++ struct sip_participant *participant; ++ ++ AST_LIST_TRAVERSE(&conference->participants, participant, entry) { ++ ast_bridge_channel_queue_playfile(ast_channel_internal_bridge_channel(participant->chan), NULL, "confbridge-join", NULL); ++ } ++ } ++ ++ ast_verb(3, "%s joined ad-hoc conference %d\n", ast_channel_name(chan), conference->confid); ++ ++ if (administrator) { ++ struct ast_party_connected_line connected; ++ ++ ast_party_connected_line_init(&connected); ++ ++ connected.id.name.str = ast_strdup("Conference"); ++ connected.id.name.valid = 1; ++ ++ connected.id.number.str = ast_strdup(""); ++ connected.id.number.valid = 1; ++ ++ connected.id.name.presentation = connected.id.number.presentation = AST_PRES_ALLOWED | AST_PRES_USER_NUMBER_PASSED_SCREEN; ++ connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_CONFERENCE; ++ ++ ast_channel_update_connected_line(chan, &connected, NULL); ++ ast_party_connected_line_free(&connected); ++ } ++ ++ return 0; ++} ++ ++/*! \brief add channels to conference */ ++static void *conference_thread(void *obj) ++{ ++ struct conference_data *conference_data = obj; ++ struct sip_pvt *pvt; ++ struct ast_channel *chan, *bridged; ++ struct sip_conference *conference = NULL; ++ struct ast_str *content = ast_str_alloca(8192); ++ int res = -1; ++ ++ if (!(pvt = get_sip_pvt(conference_data->callid, conference_data->tag, conference_data->theirtag))) { ++ ast_debug(1, "call leg does not exist\n"); ++ goto conference_cleanup; ++ } ++ ++ sip_pvt_lock(pvt); ++ ++ /* Is this a new ad-hoc conference? */ ++ if (!pvt->conference) { ++ if (create_conference(pvt)) { ++ ast_debug(1, "unable to create conference\n"); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ goto conference_cleanup; ++ } ++ ++ /* Increase ref on conference so we don't need to keep a ref on it's parent dialog */ ++ conference = pvt->conference; ++ ao2_ref(conference, +1); ++ ++ if (!(chan = pvt->owner)) { ++ ast_debug(1, "no owner channel\n"); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ goto conference_cleanup; ++ } ++ ++ ast_channel_ref(chan); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ ++ if (!(bridged = ast_channel_bridge_peer(chan))) { ++ ast_debug(1, "no bridged channel\n"); ++ ast_channel_unref(chan); ++ goto conference_cleanup; ++ } ++ ++ if (join_conference(conference, chan, 1)) { ++ ast_channel_unref(chan); ++ ast_channel_unref(bridged); ++ goto conference_cleanup; ++ } ++ ++ ast_indicate(bridged, AST_CONTROL_UNHOLD); ++ ++ if (join_conference(conference, bridged, 0)) { ++ ast_channel_unref(chan); ++ ast_channel_unref(bridged); ++ goto conference_cleanup; ++ } ++ ++ ast_channel_unref(chan); ++ ast_channel_unref(bridged); ++ } else { ++ conference = pvt->conference; ++ ao2_ref(conference, +1); ++ ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ } ++ ++ if (!conference_data->joining) { ++ if (!(pvt = get_sip_pvt(conference_data->join_callid, conference_data->join_tag, conference_data->join_theirtag))) { ++ ast_debug(1, "join call leg does not exist\n"); ++ goto conference_cleanup; ++ } ++ ++ sip_pvt_lock(pvt); ++ ++ if (!(chan = pvt->owner)) { ++ ast_debug(1, "no owner channel\n"); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ goto conference_cleanup; ++ } ++ ++ ast_channel_ref(chan); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ ++ if (!(bridged = ast_channel_bridge_peer(chan))) { ++ ast_debug(1, "no bridged channel\n"); ++ ast_channel_unref(chan); ++ goto conference_cleanup; ++ } ++ ++ ast_indicate(bridged, AST_CONTROL_UNHOLD); ++ ++ if (join_conference(conference, bridged, 0)) { ++ ast_channel_unref(chan); ++ ast_channel_unref(bridged); ++ goto conference_cleanup; ++ } ++ ++ ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); ++ ++ ast_channel_unref(chan); ++ ast_channel_unref(bridged); ++ ++ res = 0; ++ ++ /* We need to signal to the phone to take the first call leg off hold, even though the generator on that ++ channel has gone due to the masquerade as the phone still thinks that it is on hold */ ++ if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ goto conference_cleanup; ++ } ++ copy_pvt_data(pvt, conference_data->pvt); ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", conference_data->callid); ++ ast_str_append(&content, 0, "%s\n", conference_data->theirtag); ++ ast_str_append(&content, 0, "%s\n", conference_data->tag); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ sip_pvt_lock(pvt); ++ transmit_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ } else { ++ struct sip_selected *selected; ++ ++ AST_LIST_TRAVERSE(&conference_data->selected, selected, entry) { ++ /* Skip the join dialog as that was added to the conference above */ ++ if (!strcmp(conference_data->callid, selected->callid) && !strcmp(conference_data->tag, selected->tag) && !strcmp(conference_data->theirtag, selected->theirtag)) { ++ continue; ++ } ++ ++ if (!(pvt = get_sip_pvt(selected->callid, selected->tag, selected->theirtag))) { ++ ast_debug(1, "call leg does not exist\n"); ++ continue; ++ } ++ ++ sip_pvt_lock(pvt); ++ ++ if (!(chan = pvt->owner)) { ++ ast_debug(1, "no owner channel\n"); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ goto conference_cleanup; ++ } ++ ++ ast_channel_ref(chan); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ ++ if (!(bridged = ast_channel_bridge_peer(chan))) { ++ ast_debug(1, "no bridged channel\n"); ++ ast_channel_unref(chan); ++ goto conference_cleanup; ++ } ++ ++ ast_indicate(bridged, AST_CONTROL_UNHOLD); ++ ++ if (join_conference(conference, bridged, 0)) { ++ ast_channel_unref(chan); ++ ast_channel_unref(bridged); ++ goto conference_cleanup; ++ } ++ ++ ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); ++ ++ ast_channel_unref(chan); ++ ast_channel_unref(bridged); ++ } ++ ++ res = 0; ++ } ++ ++conference_cleanup: ++ if (conference) { ++ ao2_ref(conference, -1); ++ } ++ ++ ast_str_reset(content); ++ ++ if (!conference_data->joining) { ++ struct sip_request req; ++ ++ sip_pvt_lock(conference_data->pvt); ++ reqprep(&req, conference_data->pvt, SIP_NOTIFY, 0, 1); ++ add_header(&req, "Event", "refer"); ++ add_header(&req, "Subscription-State", "terminated;reason=noresource"); ++ add_header(&req, "Content-Type", "application/x-cisco-remotecc-response+xml"); ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%d\n", res ? 500 : 200); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ add_content(&req, ast_str_buffer(content)); ++ send_request(conference_data->pvt, &req, XMIT_RELIABLE, conference_data->pvt->ocseq); ++ sip_pvt_unlock(conference_data->pvt); ++ } else { ++ if ((pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ copy_pvt_data(pvt, conference_data->pvt); ++ ++ if (res) { ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "notify_display\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", conference_data->callid); ++ ast_str_append(&content, 0, "%s\n", conference_data->theirtag); ++ ast_str_append(&content, 0, "%s\n", conference_data->tag); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\200S\n"); ++ ast_str_append(&content, 0, "10\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "1\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ } else { ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", conference_data->callid); ++ ast_str_append(&content, 0, "%s\n", conference_data->theirtag); ++ ast_str_append(&content, 0, "%s\n", conference_data->tag); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Join\n"); ++ ast_str_append(&content, 0, "Complete\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ } ++ ++ sip_pvt_lock(pvt); ++ transmit_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ } ++ } ++ ++ if (conference_data->joining) { ++ struct sip_selected *selected; ++ ++ while ((selected = AST_LIST_REMOVE_HEAD(&conference_data->selected, entry))) { ++ destroy_selected(selected); ++ } ++ } ++ ++ dialog_unref(conference_data->pvt, "drop conference_data->pvt"); ++ ast_string_field_free_memory(conference_data); ++ ast_free(conference_data); ++ ++ return NULL; ++} ++ ++/*! \brief Handle conference request */ ++static int handle_remotecc_conference(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ pthread_t threadid; ++ char tmp[64]; ++ struct sip_request notify_req; ++ struct conference_data *conference_data; ++ struct sip_selected *selected; ++ ++ if (!(conference_data = ast_calloc_with_stringfields(1, struct conference_data, 128))) { ++ return -1; ++ } ++ ++ dialog_ref(pvt, "copying dialog pvt into conference_data struct"); ++ conference_data->pvt = pvt; ++ conference_data->joining = !strcmp(remotecc_data->softkeyevent, "Join"); ++ ++ ast_string_field_set(conference_data, callid, remotecc_data->dialogid.callid); ++ ast_string_field_set(conference_data, tag, remotecc_data->dialogid.remotetag); ++ ast_string_field_set(conference_data, theirtag, remotecc_data->dialogid.localtag); ++ ++ if (!conference_data->joining) { ++ ast_string_field_set(conference_data, join_callid, remotecc_data->consultdialogid.callid); ++ ast_string_field_set(conference_data, join_tag, remotecc_data->consultdialogid.remotetag); ++ ast_string_field_set(conference_data, join_theirtag, remotecc_data->consultdialogid.localtag); ++ } else { ++ ao2_lock(peer); ++ while ((selected = AST_LIST_REMOVE_HEAD(&peer->selected, entry))) { ++ AST_LIST_INSERT_TAIL(&conference_data->selected, selected, entry); ++ } ++ ao2_unlock(peer); ++ } ++ ++ if (ast_pthread_create_detached_background(&threadid, NULL, conference_thread, conference_data)) { ++ dialog_unref(conference_data->pvt, "thread creation failed"); ++ ast_string_field_free_memory(conference_data); ++ ast_free(conference_data); ++ return -1; ++ } ++ ++ /* If the conference fails we send back a NOTIFY telling the phone */ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ if (!conference_data->joining) { ++ ast_set_flag(&pvt->flags[0], SIP_OUTGOING); ++ ast_set_flag(&pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); ++ ++ pvt->subscribed = REMOTECC_XML; ++ pvt->expiry = min_expiry; ++ ++ copy_request(&pvt->initreq, req); ++ initreqprep(¬ify_req, pvt, SIP_NOTIFY, NULL); ++ add_header(¬ify_req, "Event", "refer"); ++ snprintf(tmp, sizeof(tmp), "active;expires=%d", pvt->expiry); ++ add_header(¬ify_req, "Subscription-State", tmp); ++ send_request(pvt, ¬ify_req, XMIT_RELIABLE, pvt->ocseq); ++ } ++ ++ return 0; ++} ++ ++/*! \brief Handle conflist and confdetails requests */ ++static int handle_remotecc_conflist(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_pvt *refer_pvt; ++ struct sip_conference *conference = NULL; ++ struct sip_participant *participant; ++ struct ast_str *content; ++ int is79xx = strstr(sip_get_header(req, "User-Agent"), "CP79") ? 1 : 0; ++ ++ if (!ast_strlen_zero(remotecc_data->dialogid.callid)) { ++ struct sip_pvt *targetcall_pvt; ++ ++ if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { ++ ast_debug(1, "dialogid call leg does not exist\n"); ++ return -1; ++ } ++ ++ sip_pvt_lock(targetcall_pvt); ++ ++ if ((conference = targetcall_pvt->conference)) { ++ ao2_ref(conference, +1); ++ } ++ ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop pvt"); ++ } else if (remotecc_data->confid) { ++ AST_LIST_LOCK(&conferences); ++ AST_LIST_TRAVERSE(&conferences, conference, entry) { ++ if (conference->confid == remotecc_data->confid) { ++ ao2_ref(conference, +1); ++ break; ++ } ++ } ++ AST_LIST_UNLOCK(&conferences); ++ } ++ ++ if (!conference) { ++ ast_debug(1, "Unable to find conference\n"); ++ return -1; ++ } ++ ++ if (!ast_strlen_zero(remotecc_data->usercalldata) && strcmp(remotecc_data->usercalldata, "Update")) { ++ char softkey[16]; ++ int callid; ++ ++ if (!strcmp(remotecc_data->usercalldata, "Remove") || !strcmp(remotecc_data->usercalldata, "Mute")) { ++ ast_string_field_set(peer, cisco_softkey, remotecc_data->usercalldata); ++ ao2_ref(conference, -1); ++ transmit_response(pvt, "202 Accepted", req); ++ return 0; ++ } ++ ++ /* Default action is to mute/unmute */ ++ ast_copy_string(softkey, S_OR(peer->cisco_softkey, "Mute"), sizeof(softkey)); ++ ast_string_field_set(peer, cisco_softkey, ""); ++ callid = atoi(remotecc_data->usercalldata); ++ ++ ao2_lock(conference); ++ AST_LIST_TRAVERSE(&conference->participants, participant, entry) { ++ if (participant->callid == callid) { ++ if (!strcmp(softkey, "Remove")) { ++ ast_verb(3, "%s is being removed from ad-hoc conference %d\n", ++ ast_channel_name(participant->chan), conference->confid); ++ ++ ast_bridge_remove(conference->bridge, participant->chan); ++ participant->removed = 1; ++ } else if (!strcmp(softkey, "Mute")) { ++ ast_verb(3, "%s is being %s in ad-hoc conference %d\n", ++ ast_channel_name(participant->chan), participant->muted ? "unmuted" : "muted", conference->confid); ++ participant->muted = !participant->muted; ++ ++ ast_channel_lock(participant->chan); ++ ast_channel_internal_bridge_channel(participant->chan)->features->mute = participant->muted; ++ ast_channel_unlock(participant->chan); ++ } ++ break; ++ } ++ } ++ ao2_unlock(conference); ++ } ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ if (!(content = ast_str_create(8192))) { ++ ao2_ref(conference, -1); ++ return 0; ++ } ++ ++ if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ ao2_ref(conference, -1); ++ ast_free(content); ++ return 0; ++ } ++ copy_pvt_data(refer_pvt, pvt); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%d\n", REMOTECC_CONFLIST); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "StationSequenceLast\n"); ++ ast_str_append(&content, 0, "2\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "%d\n", conference->confid); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Conference\n"); ++ ++ ao2_lock(conference); ++ AST_LIST_TRAVERSE(&conference->participants, participant, entry) { ++ char *status, *callerid = NULL; ++ ++ if (participant->removed) { ++ continue; ++ } ++ ++ if (participant->muted) { ++ status = "- "; ++ } else if (participant->talking) { ++ status = "+ "; ++ } else { ++ status = ""; ++ } ++ ++ ast_channel_lock(participant->chan); ++ if (ast_strlen_zero(callerid) && ast_channel_caller(participant->chan)->id.name.valid) { ++ callerid = ast_strdupa(ast_channel_caller(participant->chan)->id.name.str); ++ } ++ if (ast_strlen_zero(callerid) && ast_channel_caller(participant->chan)->id.number.valid) { ++ callerid = ast_strdupa(ast_channel_caller(participant->chan)->id.number.str); ++ } ++ ast_channel_unlock(participant->chan); ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s%s\n", status, S_OR(callerid, CALLERID_UNKNOWN)); ++ ast_str_append(&content, 0, "UserCallData:%d:0:%d:0:%d\n", REMOTECC_CONFLIST, conference->confid, participant->callid); ++ ast_str_append(&content, 0, "\n"); ++ ++ ast_channel_unlock(participant->chan); ++ } ++ ao2_unlock(conference); ++ ++ ast_str_append(&content, 0, "Please select\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Exit\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 3 : 1); ++ ast_str_append(&content, 0, "SoftKey:Exit\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Remove\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 1 : 2); ++ ast_str_append(&content, 0, "UserCallDataSoftKey:Select:%d:0:%d:0:Remove\n", REMOTECC_CONFLIST, conference->confid); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Mute\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 2 : 3); ++ ast_str_append(&content, 0, "UserCallDataSoftKey:Select:%d:0:%d:0:Mute\n", REMOTECC_CONFLIST, conference->confid); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Update\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 4 : 4); ++ ast_str_append(&content, 0, "UserCallDataSoftKey:Update:%d:0:%d:0:Update\n", REMOTECC_CONFLIST, conference->confid); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(refer_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); ++ ao2_ref(conference, -1); ++ ast_free(content); ++ ++ return 0; ++} ++ ++/*! \brief Handle remove last conference participant requests */ ++static int handle_remotecc_rmlastconf(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_pvt *targetcall_pvt; ++ struct sip_conference *conference = NULL; ++ struct sip_participant *participant; ++ ++ if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { ++ ast_debug(1, "dialogid call leg does not exist\n"); ++ return -1; ++ } ++ ++ sip_pvt_lock(targetcall_pvt); ++ ++ if (targetcall_pvt->conference) { ++ conference = targetcall_pvt->conference; ++ ao2_ref(conference, +1); ++ } ++ ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ ++ if (!conference) { ++ ast_debug(1, "Not in a conference\n"); ++ return -1; ++ } ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ ao2_lock(conference); ++ if ((participant = AST_LIST_FIRST(&conference->participants))) { ++ ast_bridge_remove(conference->bridge, participant->chan); ++ } ++ ao2_unlock(conference); ++ ao2_ref(conference, -1); ++ ++ return 0; ++} ++ ++static void remotecc_park_notify(struct park_data *park_data, enum ast_parked_call_event_type event_type, int parkingspace, long unsigned int timeout) ++{ ++ struct ast_str *content = ast_str_alloca(8192); ++ ++ if (park_data->monitor) { ++ struct sip_request req; ++ const char *fromdomain; ++ char *parkevent; ++ ++ if (event_type == PARKED_CALL) { ++ parkevent = "parked"; ++ } else if (event_type == PARKED_CALL_REMINDER) { ++ parkevent = "reminder"; ++ } else if (event_type == PARKED_CALL_UNPARKED) { ++ parkevent = "retrieved"; ++ } else if (event_type == PARKED_CALL_TIMEOUT) { ++ parkevent = "forwarded"; ++ } else if (event_type == PARKED_CALL_GIVEUP) { ++ parkevent = "abandoned"; ++ } else if (event_type == PARKED_CALL_FAILED) { ++ parkevent = "error"; ++ } else { ++ return; ++ } ++ ++ sip_pvt_lock(park_data->pvt); ++ ++ if (!ast_test_flag(&park_data->pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED)) { ++ ast_set_flag(&park_data->pvt->flags[0], SIP_OUTGOING); ++ ast_set_flag(&park_data->pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); ++ ++ park_data->pvt->subscribed = DIALOG_INFO_XML; ++ park_data->pvt->expiry = timeout; ++ ++ initreqprep(&req, park_data->pvt, SIP_NOTIFY, NULL); ++ } else { ++ reqprep(&req, park_data->pvt, SIP_NOTIFY, 0, 1); ++ } ++ park_data->pvt->dialogver++; ++ ++ add_header(&req, "Event", "refer"); ++ if (event_type == PARKED_CALL || event_type == PARKED_CALL_REMINDER) { ++ char tmp[64]; ++ ++ snprintf(tmp, sizeof(tmp), "active;expires=%d", park_data->pvt->expiry); ++ add_header(&req, "Subscription-State", tmp); ++ } else { ++ add_header(&req, "Subscription-State", "terminated;reason=noresource"); ++ } ++ add_header(&req, "Content-Type", "application/dialog-info+xml"); ++ fromdomain = S_OR(park_data->pvt->fromdomain, ast_sockaddr_stringify_host_remote(&park_data->pvt->ourip)); ++ ++ ast_str_append(&content, 0, "\n"); ++ /* "parmams" is a typo in the the Cisco API, duh. */ ++ ast_str_append(&content, 0, "\n", park_data->pvt->dialogver, parkingspace, fromdomain); ++ ast_str_append(&content, 0, "\n", parkingspace); ++ if (event_type == PARKED_CALL || event_type == PARKED_CALL_REMINDER) { ++ ast_str_append(&content, 0, "confirmed\n"); ++ } else { ++ ast_str_append(&content, 0, "terminated\n"); ++ } ++ ast_str_append(&content, 0, "%s\n", parkevent); ++ ast_str_append(&content, 0, "sip:%d@%s\n", parkingspace, fromdomain); ++ ast_str_append(&content, 0, "sip:%d@%s\n", parkingspace, fromdomain); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ add_content(&req, ast_str_buffer(content)); ++ send_request(park_data->pvt, &req, XMIT_RELIABLE, park_data->pvt->ocseq++); ++ sip_pvt_unlock(park_data->pvt); ++ } else { ++ struct sip_pvt *pvt; ++ ++ if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return; ++ } ++ copy_pvt_data(pvt, park_data->pvt); ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "notify_display\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", park_data->callid); ++ ast_str_append(&content, 0, "%s\n", park_data->theirtag); ++ ast_str_append(&content, 0, "%s\n", park_data->tag); ++ ast_str_append(&content, 0, "\n"); ++ if (event_type == PARKED_CALL) { ++ ast_str_append(&content, 0, "\200! %d\n", parkingspace); ++ } else { ++ ast_str_append(&content, 0, "\200^\n"); ++ } ++ ast_str_append(&content, 0, "10\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "1\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ sip_pvt_lock(pvt); ++ transmit_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ } ++} ++ ++static void park_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) ++{ ++ struct park_data *park_data = data; ++ struct ast_parked_call_payload *payload; ++ ++ if (stasis_message_type(message) != ast_parked_call_type()) { ++ return; ++ } ++ ++ if (!stasis_subscription_final_message(sub, message)) { ++ payload = stasis_message_data(message); ++ ++ if (strcmp(park_data->uniqueid, payload->parkee->base->uniqueid)) { ++ return; ++ } ++ ++ /* Send notification before hanging up the call so the dialog still exists on the phone */ ++ remotecc_park_notify(park_data, payload->event_type, payload->parkingspace, payload->timeout); ++ ++ if (payload->event_type == PARKED_CALL) { ++ ast_softhangup(park_data->chan, AST_SOFTHANGUP_EXPLICIT); ++ ast_channel_unref(park_data->chan); ++ park_data->chan = NULL; ++ } ++ ++ if (park_data->monitor && (payload->event_type == PARKED_CALL || payload->event_type == PARKED_CALL_REMINDER)) { ++ return; ++ } ++ } ++ ++ stasis_unsubscribe(sub); ++ ast_channel_cleanup(park_data->chan); ++ ++ dialog_unref(park_data->pvt, "drop park_data->pvt"); ++ ast_string_field_free_memory(park_data); ++ ast_free(park_data); ++} ++ ++/*! \brief park call */ ++static void *park_thread(void *obj) ++{ ++ struct park_data *park_data = obj; ++ struct sip_pvt *pvt; ++ struct ast_channel *chan, *bridged; ++ struct stasis_subscription *sub = NULL; ++ struct ast_exten *exten; ++ struct pbx_find_info find_info = { .stacklen = 0 }; ++ int res = -1; ++ ++ if (!(pvt = get_sip_pvt(park_data->callid, park_data->tag, park_data->theirtag))) { ++ ast_debug(1, "call leg does not exist\n"); ++ goto park_cleanup; ++ } ++ ++ sip_pvt_lock(pvt); ++ ++ if (!(chan = pvt->owner)) { ++ ast_debug(1, "no owner channel\n"); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ goto park_cleanup; ++ } ++ ++ ast_channel_ref(chan); ++ sip_pvt_unlock(pvt); ++ dialog_unref(pvt, "drop pvt"); ++ ++ if (!(bridged = ast_channel_bridge_peer(chan))) { ++ ast_debug(1, "no bridge channel"); ++ ast_channel_unref(chan); ++ goto park_cleanup; ++ } ++ ++ /* needed so that comebacktoorigin will work */ ++ pbx_builtin_setvar_helper(bridged, "BLINDTRANSFER", ast_channel_name(chan)); ++ pbx_builtin_setvar_helper(bridged, "PARKINGLOT", ast_channel_parkinglot(chan)); ++ ++ park_data->chan = chan; ++ ast_channel_ref(chan); ++ ast_string_field_set(park_data, uniqueid, ast_channel_uniqueid(bridged)); ++ ++ sub = stasis_subscribe(ast_parking_topic(), park_stasis_cb, park_data); ++ ++ exten = pbx_find_extension(NULL, NULL, &find_info, park_data->context, "park", 1, NULL, NULL, E_MATCH); ++ ast_bridge_channel_write_park(ast_channel_internal_bridge_channel(chan), ++ ast_channel_uniqueid(bridged), ast_channel_uniqueid(chan), exten ? ast_get_extension_app_data(exten) : NULL); ++ ++ ast_channel_unref(chan); ++ ast_channel_unref(bridged); ++ ++ transmit_response(park_data->pvt, "202 Accepted", &park_data->pvt->initreq); ++ res = 0; ++ ++park_cleanup: ++ if (res) { ++ transmit_response(park_data->pvt, "503 Service Unavailable", &park_data->pvt->initreq); ++ remotecc_park_notify(park_data, PARKED_CALL_FAILED, 0, 0); ++ ++ if (sub) { ++ stasis_unsubscribe(sub); ++ } ++ if (park_data->chan) { ++ ast_channel_unref(park_data->chan); ++ } ++ dialog_unref(park_data->pvt, "drop park_data->pvt"); ++ ast_string_field_free_memory(park_data); ++ ast_free(park_data); ++ } ++ ++ return NULL; ++} ++ ++/*! \brief Handle remotecc park requests */ ++static int handle_remotecc_park(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ pthread_t threadid; ++ struct park_data *park_data; ++ ++ if (!(park_data = ast_calloc_with_stringfields(1, struct park_data, 128))) { ++ return -1; ++ } ++ ++ dialog_ref(pvt, "copying dialog pvt into park_data struct"); ++ park_data->pvt = pvt; ++ copy_request(&pvt->initreq, req); ++ park_data->monitor = !strcmp(remotecc_data->softkeyevent, "ParkMonitor"); ++ ++ ast_string_field_set(park_data, context, peer->context); ++ ast_string_field_set(park_data, callid, remotecc_data->dialogid.callid); ++ ast_string_field_set(park_data, tag, remotecc_data->dialogid.remotetag); ++ ast_string_field_set(park_data, theirtag, remotecc_data->dialogid.localtag); ++ ++ if (ast_pthread_create_detached_background(&threadid, NULL, park_thread, park_data)) { ++ dialog_unref(park_data->pvt, "thread creation failed"); ++ ast_string_field_free_memory(park_data); ++ ast_free(park_data); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++/*! \brief Handle remotecc parkmonitor requests */ ++static int handle_remotecc_parkmonitor(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ return handle_remotecc_park(pvt, req, peer, remotecc_data); ++} ++ ++static int wait_for_recording(void *obj) ++{ ++ struct sip_pvt *pvt = obj; ++ ++ sip_pvt_lock(pvt); ++ if (ast_channel_state(pvt->owner) != AST_STATE_UP) { ++ sip_pvt_unlock(pvt); ++ return -1; ++ } ++ ++ if (!ast_test_flag(&pvt->flags[2], SIP_PAGE3_CISCO_RECORDING)) { ++ sip_pvt_unlock(pvt); ++ return -1; ++ } ++ ++ sip_pvt_unlock(pvt); ++ return 0; ++} ++ ++static void *record_thread(void *obj) ++{ ++ struct record_data *record_data = obj; ++ struct sip_pvt *pvt, *targetcall_pvt; ++ struct ast_channel *chan; ++ struct ast_format_cap *cap; ++ struct ast_party_connected_line connected; ++ char *peername, *uniqueid, *channame; ++ int cause; ++ ++ if (!(targetcall_pvt = get_sip_pvt(record_data->callid, record_data->tag, record_data->theirtag))) { ++ ast_debug(1, "call leg does not exist\n"); ++ goto record_cleanup; ++ } ++ ++ sip_pvt_lock(targetcall_pvt); ++ ++ if (!(chan = targetcall_pvt->owner)) { ++ ast_debug(1, "no owner channel\n"); ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ goto record_cleanup; ++ } ++ ++ peername = ast_strdupa(targetcall_pvt->peername); ++ channame = ast_strdupa(ast_channel_name(chan)); ++ uniqueid = ast_strdupa(ast_channel_uniqueid(chan)); ++ ++ cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); ++ ast_format_cap_append(cap, ast_channel_readformat(chan), 0); ++ ++ sip_pvt_unlock(targetcall_pvt); ++ ++ if (!(chan = ast_request("SIP", cap, NULL, NULL, peername, &cause))) { ++ ast_debug(1, "unable to request channel\n"); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ goto record_cleanup; ++ } ++ ++ ao2_ref(cap, -1); ++ pvt = ast_channel_tech_pvt(chan); ++ ++ ast_string_field_set(pvt, join_callid, record_data->callid); ++ ast_string_field_set(pvt, join_tag, record_data->tag); ++ ast_string_field_set(pvt, join_theirtag, record_data->theirtag); ++ ++ ast_set_flag(&pvt->flags[1], SIP_PAGE2_CALL_ONHOLD_INACTIVE); ++ ast_set_flag(&pvt->flags[2], record_data->outgoing ? SIP_PAGE3_RELAY_NEAREND : SIP_PAGE3_RELAY_FAREND); ++ ++ /* We are abusing the onhold flags to set the inactive attribute in the SDP, bump the onhold counter ++ because when recording starts the reinvite code will decrement onhold when those flags are cleared */ ++ if (pvt->relatedpeer) { ++ ast_atomic_fetchadd_int(&pvt->relatedpeer->onhold, +1); ++ } ++ ++ ast_party_connected_line_set_init(&connected, ast_channel_connected(chan)); ++ connected.id.name.valid = 1; ++ connected.id.name.str = "Record"; ++ ast_channel_set_connected_line(chan, &connected, NULL); ++ ++ ast_channel_context_set(chan, pvt->context); ++ ast_channel_exten_set(chan, "record"); ++ ast_channel_priority_set(chan, 1); ++ ++ pbx_builtin_setvar_helper(chan, "RECORD_PEERNAME", peername); ++ pbx_builtin_setvar_helper(chan, "RECORD_UNIQUEID", uniqueid); ++ pbx_builtin_setvar_helper(chan, "RECORD_CHANNEL", channame); ++ pbx_builtin_setvar_helper(chan, "RECORD_DIRECTION", record_data->outgoing ? "out" : "in"); ++ ++ if (ast_call(chan, peername, 5000)) { ++ ast_debug(1, "unable to call\n"); ++ ast_hangup(chan); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ goto record_cleanup; ++ } ++ ++ if (ast_safe_sleep_conditional(chan, 5000, wait_for_recording, pvt)) { ++ ast_debug(1, "no answer\n"); ++ ast_hangup(chan); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ goto record_cleanup; ++ } ++ ++ sip_pvt_lock(targetcall_pvt); ++ ++ if (record_data->outgoing) { ++ targetcall_pvt->recordoutpvt = dialog_ref(pvt, "copying pvt into recordoutpvt"); ++ } else { ++ targetcall_pvt->recordinpvt = dialog_ref(pvt, "copying pvt into recordinpvt"); ++ } ++ ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ ++ if (!ast_check_hangup(chan)) { ++ ast_pbx_run(chan); ++ } ++ ++record_cleanup: ++ ast_string_field_free_memory(record_data); ++ ast_free(record_data); ++ ++ return NULL; ++} ++ ++static void start_record_thread(const char *callid, const char *tag, const char *theirtag, int outgoing) ++{ ++ pthread_t threadid; ++ struct record_data *record_data; ++ ++ if (!(record_data = ast_calloc_with_stringfields(1, struct record_data, 128))) { ++ return; ++ } ++ ++ ast_string_field_set(record_data, callid, callid); ++ ast_string_field_set(record_data, tag, tag); ++ ast_string_field_set(record_data, theirtag, theirtag); ++ record_data->outgoing = outgoing; ++ ++ if (ast_pthread_create_detached_background(&threadid, NULL, record_thread, record_data)) { ++ ast_string_field_free_memory(record_data); ++ ast_free(record_data); ++ } ++} ++ ++/*! \brief Handle remotecc start recording requests */ ++static int handle_remotecc_startrecording(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ transmit_response(pvt, "202 Accepted", req); ++ start_record_thread(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag, 1); ++ ++ return 0; ++} ++ ++/*! \brief Handle remotecc stop recording requests */ ++static int handle_remotecc_stoprecording(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_pvt *targetcall_pvt; ++ struct ast_channel *chan; ++ ++ if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { ++ ast_debug(1, "dialogid call leg does not exist\n"); ++ return -1; ++ } ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ if (targetcall_pvt->recordoutpvt) { ++ sip_pvt_lock(targetcall_pvt->recordoutpvt); ++ if ((chan = targetcall_pvt->recordoutpvt->owner)) { ++ ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); ++ } ++ sip_pvt_unlock(targetcall_pvt->recordoutpvt); ++ ++ sip_pvt_lock(targetcall_pvt); ++ targetcall_pvt->recordoutpvt = dialog_unref(targetcall_pvt->recordoutpvt, "drop recordoutpvt"); ++ sip_pvt_unlock(targetcall_pvt); ++ } ++ ++ if (targetcall_pvt->recordinpvt) { ++ sip_pvt_lock(targetcall_pvt->recordinpvt); ++ if ((chan = targetcall_pvt->recordinpvt->owner)) { ++ ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); ++ } ++ sip_pvt_unlock(targetcall_pvt->recordinpvt); ++ ++ sip_pvt_lock(targetcall_pvt); ++ targetcall_pvt->recordinpvt = dialog_unref(targetcall_pvt->recordinpvt, "drop recordinpvt"); ++ sip_pvt_unlock(targetcall_pvt); ++ } ++ ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ ++ return 0; ++} ++ ++static void parse_rtp_stats(struct sip_pvt *pvt, struct sip_request *req) ++{ ++ char *rxstat, *txstat; ++ int dur = 0, rxpkt = 0, rxoct = 0, txpkt = 0, txoct = 0, latepkt = 0, lostpkt = 0, avgjit = 0; ++ struct sip_peer *peer; ++ ++ rxstat = ast_strdupa(sip_get_header(req, "RTP-RxStat")); ++ while (!ast_strlen_zero(rxstat)) { ++ char *tag, *value; ++ ++ tag = strsep(&rxstat, "="); ++ if (!(value = strsep(&rxstat, ","))) { ++ break; ++ } ++ ++ if (!strcasecmp(tag, "Dur")) { ++ dur = atoi(value); ++ } else if (!strcasecmp(tag, "Pkt")) { ++ rxpkt = atoi(value); ++ } else if (!strcasecmp(tag, "Oct")) { ++ rxoct = atoi(value); ++ } else if (!strcasecmp(tag, "LatePkt")) { ++ latepkt = atoi(value); ++ } else if (!strcasecmp(tag, "LostPkt")) { ++ lostpkt = atoi(value); ++ } else if (!strcasecmp(tag, "AvgJit")) { ++ avgjit = atoi(value); ++ } ++ } ++ ++ txstat = ast_strdupa(sip_get_header(req, "RTP-TxStat")); ++ while (!ast_strlen_zero(txstat)) { ++ char *tag, *value; ++ ++ tag = strsep(&txstat, "="); ++ if (!(value = strsep(&txstat, ","))) { ++ break; ++ } ++ ++ if (!strcasecmp(tag, "Pkt")) { ++ txpkt = atoi(value); ++ } else if (!strcasecmp(tag, "Oct")) { ++ txoct = atoi(value); ++ } ++ } ++ ++ ast_verb(3, "Call Quality Report for %s\n" ++ " Duration : %d\n" ++ " Sent Packets : %d\n" ++ " Sent Bytes : %d\n" ++ " Received Packets: %d\n" ++ " Received Bytes : %d\n" ++ " Late Packets : %d\n" ++ " Lost Packets : %d\n" ++ " Average Jitter : %d\n", ++ pvt->peername, dur, txpkt, txoct, rxpkt, rxoct, latepkt, lostpkt, avgjit); ++ ++ if ((peer = sip_find_peer(pvt->peername, NULL, TRUE, FINDALLDEVICES, FALSE, 0))) { ++ send_qrt_url(peer); ++ sip_unref_peer(peer, "unref after sip_find_peer"); ++ } ++} ++ ++static void send_qrt_url(struct sip_peer *peer) ++{ ++ struct sip_pvt *pvt; ++ struct ast_str *content = ast_str_alloca(8192); ++ struct ast_str *url = ast_str_alloca(1024); ++ ++ if (ast_strlen_zero(peer->cisco_qrt_url)) { ++ return; ++ } ++ ++ ast_str_set(&url, 0, "%s", peer->cisco_qrt_url); ++ ast_str_append(&url, 0, "%sname=%s", strchr(ast_str_buffer(url), '?') ? "&" : "?", peer->cisco_devicename); ++ ++ if (!((pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0)))) { ++ return; ++ } ++ ++ set_socket_transport(&pvt->socket, 0); ++ if (create_addr_from_peer(pvt, peer)) { ++ dialog_unlink_all(pvt); ++ dialog_unref(pvt, "create_addr_from_peer failed"); ++ return; ++ } ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "StationSequenceLast\n"); ++ ast_str_append(&content, 0, "2\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n", ast_str_buffer(url)); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++} ++ ++/*! \brief Handle remotecc quality reporting tool requests */ ++static int handle_remotecc_qrt(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_pvt *targetcall_pvt, *refer_pvt; ++ struct ast_str *content = ast_str_alloca(8192); ++ ++ if (!ast_strlen_zero(remotecc_data->dialogid.callid)) { ++ /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ ++ if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { ++ ast_debug(1, "dialogid call leg does not exist\n"); ++ return -1; ++ } ++ ++ ast_set_flag(&targetcall_pvt->flags[2], SIP_PAGE3_RTP_STATS_ON_BYE); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return 0; ++ } ++ copy_pvt_data(refer_pvt, pvt); ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "notify_display\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.callid); ++ ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.localtag); ++ ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.remotetag); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Quality Reporting Tool is active\n"); ++ ast_str_append(&content, 0, "5\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "1\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ transmit_refer_with_content(refer_pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); ++ dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); ++ } else { ++ transmit_response(pvt, "202 Accepted", req); ++ send_qrt_url(peer); ++ } ++ ++ return 0; ++} ++ ++/*! \brief Handle remotecc malicious call requests */ ++static int handle_remotecc_mcid(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_pvt *targetcall_pvt, *refer_pvt; ++ struct ast_channel *chan, *bridged; ++ struct ast_str *content = ast_str_alloca(8192); ++ ++ /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ ++ if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { ++ ast_debug(1, "dialogid call leg does not exist\n"); ++ return -1; ++ } ++ ++ sip_pvt_lock(targetcall_pvt); ++ ++ if (!(chan = targetcall_pvt->owner)) { ++ ast_debug(1, "no owner channel\n"); ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ return -1; ++ } ++ ++ ast_channel_ref(chan); ++ ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ if ((bridged = ast_channel_bridge_peer(chan))) { ++ ast_queue_control(chan, AST_CONTROL_MCID); ++ ast_verb(3, "%s has a malicious call from '%s'\n", targetcall_pvt->peername, ast_channel_name(bridged)); ++ ast_channel_unref(bridged); ++ } ++ ++ ast_channel_unref(chan); ++ ++ if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return 0; ++ } ++ copy_pvt_data(refer_pvt, pvt); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "notify_display\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.callid); ++ ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.localtag); ++ ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.remotetag); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\200T\n"); ++ ast_str_append(&content, 0, "10\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "1\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.callid); ++ ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.localtag); ++ ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.remotetag); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "DtZipZip\n"); ++ ast_str_append(&content, 0, "all\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(refer_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); ++ ++ return 0; ++} ++ ++static int remotecc_callback_notify(const char *context, const char *exten, struct ast_state_cb_info *info, void *data) ++{ ++ struct sip_peer *peer = data; ++ struct sip_pvt *pvt; ++ struct ast_str *content = ast_str_alloca(8192); ++ int is79xx = strstr(peer->useragent, "CP79") ? 1 : 0; ++ struct timeval tv; ++ struct ast_tm tm; ++ char date[32]; ++ ++ if (info->exten_state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY) || info->presence_state == AST_PRESENCE_DND) { ++ peer->callback->busy = 1; ++ return 0; ++ } else if (info->exten_state != AST_EXTENSION_NOT_INUSE || info->presence_state != AST_PRESENCE_AVAILABLE || !peer->callback->busy) { ++ return 0; ++ } ++ ++ tv = ast_tvnow(); ++ ast_strftime(date, sizeof(date), "%X %x", ast_localtime(&tv, &tm, NULL)); ++ ++ if (!((pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0)))) { ++ return 0; ++ } ++ ++ set_socket_transport(&pvt->socket, 0); ++ if (create_addr_from_peer(pvt, peer)) { ++ dialog_unlink_all(pvt); ++ dialog_unref(pvt, "create_addr_from_peer failed"); ++ return 0; ++ } ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "StationSequenceLast\n"); ++ ast_str_append(&content, 0, "2\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ ++ ast_str_reset(content); ++ ++ if (!((pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0)))) { ++ return 0; ++ } ++ ++ set_socket_transport(&pvt->socket, 0); ++ if (create_addr_from_peer(pvt, peer)) { ++ dialog_unlink_all(pvt); ++ dialog_unref(pvt, "create_addr_from_peer failed"); ++ return 0; ++ } ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%d\n", REMOTECC_CALLBACK); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "StationSequenceLast\n"); ++ ast_str_append(&content, 0, "2\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "CallBack\n"); ++ ast_str_append(&content, 0, "%s is now available at %s.\n\nPress Dial to call.\nPress Cancel to deactivate.\nPress Exit to quit this screen.\n", peer->callback->exten, date); ++ ast_str_append(&content, 0, "Please select\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Exit\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 3 : 1); ++ ast_str_append(&content, 0, "SoftKey:Exit\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Cancel\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 2 : 2); ++ ast_str_append(&content, 0, "UserCallData:%d:0:0:0:Cancel\n", REMOTECC_CALLBACK); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Dial\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 1 : 3); ++ ast_str_append(&content, 0, "UserCallData:%d:0:0:0:Dial\n", REMOTECC_CALLBACK); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ ++ return 0; ++} ++ ++/*! \brief Handle remotecc callback requests */ ++static int handle_remotecc_callback(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_pvt *refer_pvt; ++ struct ast_str *content = ast_str_alloca(8192); ++ int is79xx = strstr(sip_get_header(req, "User-Agent"), "CP79") ? 1 : 0; ++ ++ if (!ast_strlen_zero(remotecc_data->dialogid.callid)) { ++ struct sip_pvt *targetcall_pvt; ++ struct ast_channel *chan = NULL; ++ char *exten, *subtype = NULL, *message = NULL; ++ int exten_state, presence_state; ++ int res = -1; ++ ++ /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ ++ if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { ++ ast_debug(1, "dialogid call leg does not exist\n"); ++ return -1; ++ } ++ ++ sip_pvt_lock(targetcall_pvt); ++ ++ if (!(chan = targetcall_pvt->owner)) { ++ ast_debug(1, "no owner channel\n"); ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ return -1; ++ } ++ ++ ast_channel_ref(chan); ++ sip_pvt_unlock(targetcall_pvt); ++ dialog_unref(targetcall_pvt, "drop targetcall_pvt"); ++ ++ ast_channel_lock(chan); ++ exten = ast_strdupa(S_COR(ast_channel_connected(chan)->id.number.valid, ast_channel_connected(chan)->id.number.str, targetcall_pvt->exten)); ++ ast_channel_unlock(chan); ++ ast_channel_unref(chan); ++ ++ if (peer->callback) { ++ destroy_callback(peer); ++ peer->callback = NULL; ++ } ++ ++ if (ast_strlen_zero(exten)) { ++ goto callback_cleanup; ++ } ++ if (!(peer->callback = ast_calloc(1, sizeof(*peer->callback)))) { ++ goto callback_cleanup; ++ } ++ if (!(peer->callback->exten = ast_strdup(exten))) { ++ ast_free(peer->callback); ++ peer->callback = NULL; ++ goto callback_cleanup; ++ } ++ sip_ref_peer(peer, "copying peer into callback struct"); ++ if (!(peer->callback->stateid = ast_extension_state_add(peer->context, peer->callback->exten, remotecc_callback_notify, peer))) { ++ sip_unref_peer(peer, "copying peer into callback struct failed"); ++ ast_free(peer->callback->exten); ++ ast_free(peer->callback); ++ goto callback_cleanup; ++ } ++ ++ exten_state = ast_extension_state(NULL, peer->context, peer->callback->exten); ++ presence_state = ast_hint_presence_state(NULL, peer->context, peer->callback->exten, &subtype, &message); ++ ++ ast_free(subtype); ++ ast_free(message); ++ ++ if (exten_state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY) || presence_state == AST_PRESENCE_DND) { ++ peer->callback->busy = 1; ++ } ++ ++ ast_channel_hangupcause_set(chan, AST_CAUSE_FAILURE); ++ ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); ++ res = 0; ++ ++ callback_cleanup: ++ if (res) { ++ transmit_response(pvt, "202 Accepted", req); ++ ++ if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return 0; ++ } ++ copy_pvt_data(refer_pvt, pvt); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "StationSequenceLast\n"); ++ ast_str_append(&content, 0, "2\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "CallBack\n"); ++ ast_str_append(&content, 0, "Unable to activate callback on %s\n", S_OR(exten, CALLERID_UNKNOWN)); ++ ast_str_append(&content, 0, "Please select\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Exit\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 3 : 1); ++ ast_str_append(&content, 0, "SoftKey:Exit\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(refer_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); ++ ++ return 0; ++ } ++ } else if (!ast_strlen_zero(remotecc_data->usercalldata) && peer->callback) { ++ transmit_response(pvt, "202 Accepted", req); ++ ++ if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return 0; ++ } ++ copy_pvt_data(refer_pvt, pvt); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "StationSequenceLast\n"); ++ ast_str_append(&content, 0, "2\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(refer_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); ++ ++ if (!strcmp(remotecc_data->usercalldata, "Dial")) { ++ ast_str_reset(content); ++ ++ if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return 0; ++ } ++ copy_pvt_data(refer_pvt, pvt); ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", peer->callback->exten); ++ ast_str_append(&content, 0, "%d\n", peer->cisco_lineindex); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ transmit_refer_with_content(refer_pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); ++ dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); ++ ++ destroy_callback(peer); ++ peer->callback = NULL; ++ } else if (!strcmp(remotecc_data->usercalldata, "Cancel")) { ++ destroy_callback(peer); ++ peer->callback = NULL; ++ } ++ ++ return 0; ++ } ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return 0; ++ } ++ copy_pvt_data(refer_pvt, pvt); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%d\n", REMOTECC_CALLBACK); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "StationSequenceLast\n"); ++ ast_str_append(&content, 0, "2\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "CallBack\n"); ++ ++ if (peer->callback) { ++ ast_str_append(&content, 0, "CallBack is activated on %s.\n\nPress Cancel to deactivate.\nPress Exit to quit this screen.", peer->callback->exten); ++ } else { ++ ast_str_append(&content, 0, "CallBack is not activated."); ++ } ++ ++ ast_str_append(&content, 0, "Please select\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Exit\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 3 : 1); ++ ast_str_append(&content, 0, "SoftKey:Exit\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ if (peer->callback) { ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "Cancel\n"); ++ ast_str_append(&content, 0, "%d\n", is79xx ? 2 : 2); ++ ast_str_append(&content, 0, "UserCallData:%d:0:0:0:Cancel\n", REMOTECC_CALLBACK); ++ ast_str_append(&content, 0, "\n"); ++ } ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(refer_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); ++ ++ return 0; ++} ++ ++/*! \brief Handle remotecc select requests */ ++static int handle_remotecc_select(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_selected *selected; ++ int found = 0; ++ ++ ao2_lock(peer); ++ AST_LIST_TRAVERSE(&peer->selected, selected, entry) { ++ if (!strcmp(remotecc_data->dialogid.callid, selected->callid) && !strcmp(remotecc_data->dialogid.remotetag, selected->tag) && !strcmp(remotecc_data->dialogid.localtag, selected->theirtag)) { ++ found = 1; ++ break; ++ } ++ } ++ ao2_unlock(peer); ++ ++ if (!found) { ++ if (!(selected = ast_calloc_with_stringfields(1, struct sip_selected, 128))) { ++ return -1; ++ } ++ ++ ast_string_field_set(selected, callid, remotecc_data->dialogid.callid); ++ ast_string_field_set(selected, tag, remotecc_data->dialogid.remotetag); ++ ast_string_field_set(selected, theirtag, remotecc_data->dialogid.localtag); ++ ++ ao2_lock(peer); ++ AST_LIST_INSERT_TAIL(&peer->selected, selected, entry); ++ ao2_unlock(peer); ++ } ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ return 0; ++} ++ ++/*! \brief Handle remotecc unselect requests */ ++static int handle_remotecc_unselect(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ struct sip_selected *selected; ++ ++ ao2_lock(peer); ++ AST_LIST_TRAVERSE_SAFE_BEGIN(&peer->selected, selected, entry) { ++ if (!strcmp(remotecc_data->dialogid.callid, selected->callid) && !strcmp(remotecc_data->dialogid.remotetag, selected->tag) && !strcmp(remotecc_data->dialogid.localtag, selected->theirtag)) { ++ AST_LIST_REMOVE_CURRENT(entry); ++ destroy_selected(selected); ++ break; ++ } ++ } ++ AST_LIST_TRAVERSE_SAFE_END; ++ ao2_unlock(peer); ++ ++ transmit_response(pvt, "202 Accepted", req); ++ ++ return 0; ++} ++ ++/*! \brief Handle remotecc join requests */ ++static int handle_remotecc_join(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) ++{ ++ return handle_remotecc_conference(pvt, req, peer, remotecc_data); ++} ++ ++/*! \brief Handle incoming remotecc request */ ++static int handle_refer_remotecc(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer) ++{ ++#ifdef HAVE_LIBXML2 ++ const char *content_type = sip_get_header(req, "Content-Type"); ++ char *remotecc_body; ++ struct ast_xml_doc *remotecc_doc = NULL; ++ struct ast_xml_node *remotecc_request_node, *remotecc_request_children; ++ struct ast_xml_node *softkeyeventmsg_node, *softkeyeventmsg_children; ++ struct ast_xml_node *datapassthroughreq_node, *datapassthroughreq_children; ++ struct remotecc_data remotecc_data; ++ int start, end, done = 0; ++ char *boundary = NULL; ++ ++ if (!strncasecmp(content_type, "multipart/mixed", 15)) { ++ if ((boundary = strcasestr(content_type, ";boundary="))) { ++ boundary += 10; ++ } else if ((boundary = strcasestr(content_type, "; boundary="))) { ++ boundary += 11; ++ } else { ++ return -1; ++ } ++ boundary = ast_strdupa(boundary); ++ boundary = strsep(&boundary, ";"); ++ ++ if ((start = find_boundary(req, boundary, 0, &done)) == -1) { ++ return -1; ++ } ++ start += 1; ++ if ((end = find_boundary(req, boundary, start, &done)) == -1) { ++ return -1; ++ } ++ ++ content_type = NULL; ++ while (start < end) { ++ const char *line = REQ_OFFSET_TO_STR(req, line[start++]); ++ ++ if (!strncasecmp(line, "Content-Type:", 13)) { ++ content_type = ast_skip_blanks(line + 13); ++ } else if (ast_strlen_zero(line)) { ++ break; ++ } ++ } ++ ++ if (ast_strlen_zero(content_type)) { ++ return -1; ++ } ++ } else { ++ start = 0; ++ end = req->lines; ++ } ++ ++ if (strcasecmp(content_type, "application/x-cisco-remotecc-request+xml")) { ++ ast_log(LOG_WARNING, "Content type is not x-cisco-remotecc-request+xml\n"); ++ return -1; ++ } ++ ++ if (!(remotecc_body = get_content(req, start, end - 1))) { ++ ast_log(LOG_WARNING, "Unable to get remotecc body\n"); ++ return -1; ++ } ++ ++ if (!(remotecc_doc = ast_xml_read_memory(remotecc_body, strlen(remotecc_body)))) { ++ ast_log(LOG_WARNING, "Unable to open XML remotecc document. Is it malformed?\n"); ++ return -1; ++ } ++ ++ if (!(remotecc_request_node = ast_xml_get_root(remotecc_doc))) { ++ ast_log(LOG_WARNING, "Unable to get root node\n"); ++ ast_xml_close(remotecc_doc); ++ return -1; ++ } ++ ++ if (strcasecmp(ast_xml_node_get_name(remotecc_request_node), "x-cisco-remotecc-request")) { ++ ast_log(LOG_WARNING, "Missing x-cisco-remotecc-request node\n"); ++ ast_xml_close(remotecc_doc); ++ return -1; ++ } ++ ++ if (!(remotecc_request_children = ast_xml_node_get_children(remotecc_request_node))) { ++ ast_log(LOG_WARNING, "No tuples in x-cisco-remotecc-request node\n"); ++ ast_xml_close(remotecc_doc); ++ return -1; ++ } ++ ++ memset(&remotecc_data, 0, sizeof(remotecc_data)); ++ ++ if ((softkeyeventmsg_node = ast_xml_find_element(remotecc_request_children, "softkeyeventmsg", NULL, NULL)) && ++ (softkeyeventmsg_children = ast_xml_node_get_children(softkeyeventmsg_node))) { ++ struct ast_xml_node *softkeyevent_node; ++ struct ast_xml_node *dialogid_node, *dialogid_children; ++ struct ast_xml_node *consultdialogid_node, *consultdialogid_children; ++ struct ast_xml_node *joindialogid_node, *joindialogid_children; ++ struct ast_xml_node *callid_node, *localtag_node, *remotetag_node; ++ const char *softkeyevent_text, *callid_text, *localtag_text, *remotetag_text; ++ ++ if ((softkeyevent_node = ast_xml_find_element(softkeyeventmsg_children, "softkeyevent", NULL, NULL))) { ++ softkeyevent_text = ast_xml_get_text(softkeyevent_node); ++ remotecc_data.softkeyevent = ast_strdupa(softkeyevent_text); ++ ast_xml_free_text(softkeyevent_text); ++ } ++ ++ if ((dialogid_node = ast_xml_find_element(softkeyeventmsg_children, "dialogid", NULL, NULL)) && ++ (dialogid_children = ast_xml_node_get_children(dialogid_node))) { ++ if ((callid_node = ast_xml_find_element(dialogid_children, "callid", NULL, NULL))) { ++ callid_text = ast_xml_get_text(callid_node); ++ remotecc_data.dialogid.callid = ast_strdupa(callid_text); ++ ast_xml_free_text(callid_text); ++ } ++ ++ if ((localtag_node = ast_xml_find_element(dialogid_children, "localtag", NULL, NULL))) { ++ localtag_text = ast_xml_get_text(localtag_node); ++ remotecc_data.dialogid.localtag = ast_strdupa(localtag_text); ++ ast_xml_free_text(localtag_text); ++ } ++ ++ if ((remotetag_node = ast_xml_find_element(dialogid_children, "remotetag", NULL, NULL))) { ++ remotetag_text = ast_xml_get_text(remotetag_node); ++ remotecc_data.dialogid.remotetag = ast_strdupa(remotetag_text); ++ ast_xml_free_text(remotetag_text); ++ } ++ } ++ ++ if ((consultdialogid_node = ast_xml_find_element(softkeyeventmsg_children, "consultdialogid", NULL, NULL)) && ++ (consultdialogid_children = ast_xml_node_get_children(consultdialogid_node))) { ++ if ((callid_node = ast_xml_find_element(consultdialogid_children, "callid", NULL, NULL))) { ++ callid_text = ast_xml_get_text(callid_node); ++ remotecc_data.consultdialogid.callid = ast_strdupa(callid_text); ++ ast_xml_free_text(callid_text); ++ } ++ ++ if ((localtag_node = ast_xml_find_element(consultdialogid_children, "localtag", NULL, NULL))) { ++ localtag_text = ast_xml_get_text(localtag_node); ++ remotecc_data.consultdialogid.localtag = ast_strdupa(localtag_text); ++ ast_xml_free_text(localtag_text); ++ } ++ ++ if ((remotetag_node = ast_xml_find_element(consultdialogid_children, "remotetag", NULL, NULL))) { ++ remotetag_text = ast_xml_get_text(remotetag_node); ++ remotecc_data.consultdialogid.remotetag = ast_strdupa(remotetag_text); ++ ast_xml_free_text(remotetag_text); ++ } ++ } ++ ++ if ((joindialogid_node = ast_xml_find_element(softkeyeventmsg_children, "joindialogid", NULL, NULL)) && ++ (joindialogid_children = ast_xml_node_get_children(joindialogid_node))) { ++ if ((callid_node = ast_xml_find_element(joindialogid_children, "callid", NULL, NULL))) { ++ callid_text = ast_xml_get_text(callid_node); ++ remotecc_data.joindialogid.callid = ast_strdupa(callid_text); ++ ast_xml_free_text(callid_text); ++ } ++ ++ if ((localtag_node = ast_xml_find_element(joindialogid_children, "localtag", NULL, NULL))) { ++ localtag_text = ast_xml_get_text(localtag_node); ++ remotecc_data.joindialogid.localtag = ast_strdupa(localtag_text); ++ ast_xml_free_text(localtag_text); ++ } ++ ++ if ((remotetag_node = ast_xml_find_element(joindialogid_children, "remotetag", NULL, NULL))) { ++ remotetag_text = ast_xml_get_text(remotetag_node); ++ remotecc_data.joindialogid.remotetag = ast_strdupa(remotetag_text); ++ ast_xml_free_text(remotetag_text); ++ } ++ } ++ } else if ((datapassthroughreq_node = ast_xml_find_element(remotecc_request_children, "datapassthroughreq", NULL, NULL)) && ++ (datapassthroughreq_children = ast_xml_node_get_children(datapassthroughreq_node))) { ++ struct ast_xml_node *applicationid_node, *confid_node; ++ const char *applicationid_text, *confid_text; ++ ++ if ((applicationid_node = ast_xml_find_element(datapassthroughreq_children, "applicationid", NULL, NULL))) { ++ applicationid_text = ast_xml_get_text(applicationid_node); ++ remotecc_data.applicationid = atoi(S_OR(applicationid_text, "")); ++ ast_xml_free_text(applicationid_text); ++ } ++ ++ if ((confid_node = ast_xml_find_element(datapassthroughreq_children, "confid", NULL, NULL))) { ++ confid_text = ast_xml_get_text(confid_node); ++ remotecc_data.confid = atoi(S_OR(confid_text, "")); ++ ast_xml_free_text(confid_text); ++ } ++ } ++ ++ ast_xml_close(remotecc_doc); ++ ++ if (boundary && !done) { ++ start = end + 1; ++ if ((end = find_boundary(req, boundary, start, &done)) == -1) { ++ ast_log(LOG_WARNING, "Failed to find end boundary\n"); ++ return -1; ++ } ++ ++ content_type = NULL; ++ while (start < end) { ++ const char *line = REQ_OFFSET_TO_STR(req, line[start++]); ++ ++ if (!strncasecmp(line, "Content-Type:", 13)) { ++ content_type = ast_skip_blanks(line + 13); ++ } else if (ast_strlen_zero(line)) { ++ break; ++ } ++ } ++ ++ if (ast_strlen_zero(content_type)) { ++ return -1; ++ } ++ if (!strcasecmp(content_type, "application/x-cisco-remotecc-cm+xml")) { ++ char *usercalldata; ++ ++ if (!(usercalldata = get_content(req, start, end - 1))) { ++ ast_log(LOG_WARNING, "Unable to get usercalldata body\n"); ++ return -1; ++ } ++ ++ remotecc_data.usercalldata = ast_trim_blanks(ast_strdupa(usercalldata)); ++ } ++ } ++ ++ if (!ast_strlen_zero(remotecc_data.softkeyevent)) { ++ if (!strcmp(remotecc_data.softkeyevent, "IDivert")) { ++ return handle_remotecc_idivert(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "HLog")) { ++ return handle_remotecc_hlog(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "Conference")) { ++ return handle_remotecc_conference(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "ConfList")) { ++ return handle_remotecc_conflist(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "ConfDetails")) { ++ return handle_remotecc_conflist(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "RmLastConf")) { ++ return handle_remotecc_rmlastconf(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "Cancel")) { ++ transmit_response(pvt, "202 Accepted", req); ++ return 0; ++ } else if (!strcmp(remotecc_data.softkeyevent, "Park")) { ++ return handle_remotecc_park(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "ParkMonitor")) { ++ return handle_remotecc_parkmonitor(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "StartRecording")) { ++ return handle_remotecc_startrecording(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "StopRecording")) { ++ return handle_remotecc_stoprecording(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "QRT")) { ++ return handle_remotecc_qrt(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "MCID")) { ++ return handle_remotecc_mcid(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "CallBack")) { ++ return handle_remotecc_callback(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "Select")) { ++ return handle_remotecc_select(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "Unselect")) { ++ return handle_remotecc_unselect(pvt, req, peer, &remotecc_data); ++ } else if (!strcmp(remotecc_data.softkeyevent, "Join")) { ++ return handle_remotecc_join(pvt, req, peer, &remotecc_data); ++ } ++ ++ ast_log(LOG_WARNING, "Unsupported softkeyevent: %s\n", remotecc_data.softkeyevent); ++ } else if (remotecc_data.applicationid) { ++ if (remotecc_data.applicationid == REMOTECC_CONFLIST) { ++ return handle_remotecc_conflist(pvt, req, peer, &remotecc_data); ++ } else if (remotecc_data.applicationid == REMOTECC_CALLBACK) { ++ return handle_remotecc_callback(pvt, req, peer, &remotecc_data); ++ } ++ ++ ast_log(LOG_WARNING, "Unsupported applicationid: %d\n", remotecc_data.applicationid); ++ } else { ++ ast_log(LOG_WARNING, "Unsupported x-cisco-remotecc-request+xml request\n"); ++ } ++#endif ++ return -1; ++} ++ + /*! \brief Get tag from packet + * + * \return pointer to the provided tag buffer. +@@ -25740,16 +29423,133 @@ + return 0; + } + ++/*! \brief Handle dialog notifications */ ++static int handle_notify_dialog(struct sip_pvt *pvt, struct sip_request *req) ++{ ++#ifdef HAVE_LIBXML2 ++ struct sip_peer *peer; ++ const char *content_type = sip_get_header(req, "Content-Type"); ++ char *dialog_body, *name, *domain, *entity, *state; ++ struct ast_xml_doc *dialog_doc = NULL; ++ struct ast_xml_node *dialog_info_node, *dialog_info_children; ++ struct ast_xml_node *dialog_node, *dialog_children; ++ struct ast_xml_node *state_node; ++ const char *entity_attr, *state_text; ++ int offhook = 0; ++ ++ if (strcasecmp(content_type, "application/dialog-info+xml")) { ++ ast_log(LOG_WARNING, "Content type is not application/dialog-info+xml\n"); ++ return -1; ++ } ++ ++ if (!(dialog_body = get_content(req, 0, req->lines - 1))) { ++ ast_log(LOG_WARNING, "Unable to get dialog body\n"); ++ return -1; ++ } ++ ++ if (!(dialog_doc = ast_xml_read_memory(dialog_body, strlen(dialog_body)))) { ++ ast_log(LOG_WARNING, "Unable to open XML dialog document. Is it malformed?\n"); ++ return -1; ++ } ++ ++ if (!(dialog_info_node = ast_xml_get_root(dialog_doc))) { ++ ast_log(LOG_WARNING, "Unable to get root node\n"); ++ ast_xml_close(dialog_doc); ++ return -1; ++ } ++ ++ if (strcasecmp(ast_xml_node_get_name(dialog_info_node), "dialog-info")) { ++ ast_log(LOG_WARNING, "Missing dialog-info node\n"); ++ ast_xml_close(dialog_doc); ++ return -1; ++ } ++ ++ /* We have to use the entity attribute on dialog-info instead of the NOTIFY URI because some ++ * models of Cisco phones only put the first character of the peer name in the From/To/URI */ ++ if (!(entity_attr = ast_xml_get_attribute(dialog_info_node, "entity"))) { ++ ast_log(LOG_WARNING, "Missing entity attribute"); ++ ast_xml_close(dialog_doc); ++ return -1; ++ } ++ ++ entity = ast_strdupa(entity_attr); ++ ast_xml_free_attr(entity_attr); ++ ++ if (!(dialog_info_children = ast_xml_node_get_children(dialog_info_node))) { ++ ast_log(LOG_WARNING, "No tuples in dialog-info node\n"); ++ ast_xml_close(dialog_doc); ++ return -1; ++ } ++ ++ if (!(dialog_node = ast_xml_find_element(dialog_info_children, "dialog", NULL, NULL))) { ++ ast_log(LOG_WARNING, "Missing dialog node\n"); ++ ast_xml_close(dialog_doc); ++ return -1; ++ } ++ ++ if (!(dialog_children = ast_xml_node_get_children(dialog_node))) { ++ ast_log(LOG_WARNING, "No tuples in dialog node\n"); ++ ast_xml_close(dialog_doc); ++ return -1; ++ } ++ ++ if (!(state_node = ast_xml_find_element(dialog_children, "state", NULL, NULL))) { ++ ast_log(LOG_WARNING, "Missing state node\n"); ++ ast_xml_close(dialog_doc); ++ return -1; ++ } ++ ++ state_text = ast_xml_get_text(state_node); ++ state = ast_strdupa(state_text); ++ ast_xml_free_text(state_text); ++ ast_xml_close(dialog_doc); ++ ++ if (!strcasecmp(state, "trying")) { ++ offhook = 1; ++ } else if (!strcasecmp(state, "terminated")) { ++ offhook = -1; ++ } else { ++ ast_log(LOG_WARNING, "Invalid content in state node %s\n", state_text); ++ return -1; ++ } ++ ++ if (parse_uri_legacy_check(entity, "sip:,sips", &name, NULL, &domain, NULL)) { ++ return -1; ++ } ++ SIP_PEDANTIC_DECODE(name); ++ ++ if (!(peer = sip_find_peer(name, NULL, TRUE, FINDPEERS, TRUE, 0))) { ++ ast_log(LOG_WARNING, "Unknown peer '%s'\n", name); ++ return -1; ++ } ++ ++ if (peer->socket.type == pvt->socket.type && !ast_sockaddr_cmp(&peer->addr, &pvt->recv)) { ++ ao2_lock(peer); ++ if ((peer->offhook += offhook) < 0) { ++ peer->offhook = 0; ++ } ++ ao2_unlock(peer); ++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); ++ } ++ ++ sip_unref_peer(peer, "handle_notify_dialog: unref peer from sip_find_peer lookup"); ++ transmit_response(pvt, "200 OK", req); ++ return 0; ++#else ++ return -1; ++#endif ++} ++ + /*! \brief Handle incoming notifications */ + static int handle_request_notify(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, const char *e) + { + /* This is mostly a skeleton for future improvements */ + /* Mostly created to return proper answers on notifications on outbound REFER's */ + int res = 0; +- const char *event = sip_get_header(req, "Event"); ++ char *event = ast_strdupa(sip_get_header(req, "Event")); + char *sep; + +- if( (sep = strchr(event, ';')) ) { /* XXX bug here - overwriting string ? */ ++ if ((sep = strchr(event, ';'))) { + *sep++ = '\0'; + } + +@@ -25764,6 +29564,7 @@ + char *buf, *cmd, *code; + int respcode; + int success = TRUE; ++ const char *type = find_content_type(req); + + /* EventID for each transfer... EventID is basically the REFER cseq + +@@ -25772,7 +29573,13 @@ + Check if we have an owner of this event */ + + /* Check the content type */ +- if (strncasecmp(sip_get_header(req, "Content-Type"), "message/sipfrag", strlen("message/sipfrag"))) { ++ if (strncasecmp(type, "message/sipfrag", strlen("message/sipfrag"))) { ++ if (!strcasecmp(type, "application/x-cisco-remotecc-response+xml")) { ++ transmit_response(p, "200 OK", req); ++ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); ++ return 0; ++ } ++ + /* We need a sipfrag */ + transmit_response(p, "400 Bad request", req); + sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); +@@ -25780,7 +29587,7 @@ + } + + /* Get the text of the attachment */ +- if (ast_strlen_zero(buf = get_content(req))) { ++ if (ast_strlen_zero(buf = get_content(req, 0, req->lines))) { + ast_log(LOG_WARNING, "Unable to retrieve attachment from NOTIFY %s\n", p->callid); + transmit_response(p, "400 Bad request", req); + sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); +@@ -25894,6 +29701,11 @@ + transmit_response(p, "200 OK", req); + } else if (!strcmp(event, "call-completion")) { + res = handle_cc_notify(p, req); ++ } else if (!strcmp(event, "dialog")) { ++ if (handle_notify_dialog(p, req)) { ++ transmit_response(p, "489 Bad event", req); ++ res = -1; ++ } + } else { + /* We don't understand this event. */ + transmit_response(p, "489 Bad event", req); +@@ -27198,6 +31010,11 @@ + + ast_set_flag(&transferer->flags[0], SIP_DEFER_BYE_ON_TRANSFER); /* Delay hangup */ + ++ /* Cisco phones need a different response code */ ++ if (ast_test_flag(&targetcall_pvt->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ ast_set_flag(&targetcall_pvt->flags[2], SIP_PAGE3_TRANSFER_RESPONSE); ++ } ++ + sip_pvt_unlock(transferer); + ast_channel_unlock(transferer_chan); + *nounlock = 1; +@@ -27341,7 +31158,7 @@ + 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, uint32_t seqno, int *nounlock) ++static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, const char *e, int *nounlock) + { + char *refer_to = NULL; + char *refer_to_context = NULL; +@@ -27350,6 +31167,46 @@ + enum ast_transfer_result transfer_res; + RAII_VAR(struct ast_channel *, transferer, NULL, ast_channel_cleanup); + RAII_VAR(struct ast_str *, replaces_str, NULL, ast_free_ptr); ++ const char *type = find_content_type(req); ++ ++ /* Cisco USECALLMANAGER remotecc and failover */ ++ if (!strcasecmp(type, "application/x-cisco-alarm+xml") || !strcasecmp(type, "application/x-cisco-remotecc-response+xml")) { ++ transmit_response(p, "202 Accepted", req); ++ if (!p->owner) { ++ sip_alreadygone(p); ++ pvt_set_needdestroy(p, "alarm/remotecc device notificaton"); ++ } ++ return 0; ++ } else if (!strcasecmp(type, "application/x-cisco-remotecc-request+xml")) { ++ struct sip_peer *authpeer = NULL; ++ ++ res = check_user_full(p, req, SIP_REFER, e, 0, addr, &authpeer); ++ ++ /* if an authentication response was sent, we are done here */ ++ if (res == AUTH_CHALLENGE_SENT) { ++ return 0; ++ } ++ ++ if (res != AUTH_SUCCESSFUL) { ++ ast_log(LOG_NOTICE, "Failed to authenticate device %s for REFER\n", sip_get_header(req, "From")); ++ transmit_response_reliable(p, "403 Forbidden", req); ++ } else if (handle_refer_remotecc(p, req, authpeer)) { ++ transmit_response(p, "603 Declined (Remotecc failed)", req); ++ if (authpeer) { ++ sip_unref_peer(authpeer, "sip_unref_peer, from handle_request_refer"); ++ } ++ } ++ if (!p->owner) { ++ sip_alreadygone(p); ++ pvt_set_needdestroy(p, "remotecc request"); ++ } ++ return 0; ++ } else if (!strcasecmp(sip_get_header(req, "Refer-To"), "")) { ++ transmit_response(p, "202 Accepted", req); ++ sip_alreadygone(p); ++ pvt_set_needdestroy(p, "token registration"); ++ return 0; ++ } + + if (req->debug) { + ast_verbose("Call %s got a SIP call transfer from %s: (REFER)!\n", +@@ -27612,7 +31469,12 @@ + break; + } + } +- transmit_response_reliable(p, "487 Request Terminated", &p->initreq); ++ /* Cisco phones fail to include the To tag in the ACK response */ ++ if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ transmit_response(p, "487 Request Terminated", &p->initreq); ++ } else { ++ transmit_response_reliable(p, "487 Request Terminated", &p->initreq); ++ } + transmit_response(p, "200 OK", req); + return 1; + } else { +@@ -27829,6 +31691,10 @@ + transmit_response(p, "200 OK", req); + } + ++ if (ast_test_flag(&p->flags[2], SIP_PAGE3_RTP_STATS_ON_BYE)) { ++ parse_rtp_stats(p, req); ++ } ++ + /* Destroy any pending invites so we won't try to do another + * scheduled reINVITE. */ + stop_reinvite_retry(p); +@@ -28199,7 +32065,7 @@ + return FALSE; + } + +- if (!(pidf_body = get_content(req))) { ++ if (!(pidf_body = get_content(req, 0, req->lines))) { + ast_log(LOG_WARNING, "Unable to get PIDF body\n"); + return FALSE; + } +@@ -28319,6 +32185,102 @@ + return res; + } + ++static int presence_esc_publish_handler(struct sip_pvt *pvt, struct sip_request *req, struct event_state_compositor *esc, struct sip_esc_entry *esc_entry) ++{ ++ struct ast_xml_doc *pidf_doc = NULL; ++ struct ast_xml_node *presence_node; ++ struct ast_xml_node *presence_children; ++ struct ast_xml_node *person_node; ++ struct ast_xml_node *person_children; ++ struct ast_xml_node *activities_node; ++ struct ast_xml_node *activities_children; ++ struct ast_xml_node *dnd_node; ++ struct ast_xml_node *available_node; ++ struct sip_peer *peer; ++ struct sip_alias *alias; ++ int res = 0; ++ int donotdisturb = 0; ++ ++ if (sip_pidf_validate(req, &pidf_doc) == FALSE) { ++ res = -1; ++ } else if (ast_test_flag(&pvt->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ 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 presence_publish_cleanup; ++ } ++ ++ if (!(person_node = ast_xml_find_element(presence_children, "person", NULL, NULL))) { ++ ast_log(LOG_NOTICE, "Couldn't find person node?\n"); ++ res = -1; ++ goto presence_publish_cleanup; ++ } ++ ++ if (!(person_children = ast_xml_node_get_children(person_node))) { ++ ast_log(LOG_NOTICE, "No tuples within person node.\n"); ++ res = -1; ++ goto presence_publish_cleanup; ++ } ++ ++ if (!(activities_node = ast_xml_find_element(person_children, "activities", NULL, NULL))) { ++ ast_log(LOG_NOTICE, "Couldn't find activities node?\n"); ++ res = -1; ++ goto presence_publish_cleanup; ++ } ++ ++ if (!(activities_children = ast_xml_node_get_children(activities_node))) { ++ ast_log(LOG_NOTICE, "No tuples within activities node.\n"); ++ res = -1; ++ goto presence_publish_cleanup; ++ } ++ ++ if ((dnd_node = ast_xml_find_element(activities_children, "dnd", NULL, NULL))) { ++ donotdisturb = 1; ++ } else if ((available_node = ast_xml_find_element(activities_children, "available", NULL, NULL))) { ++ donotdisturb = 0; ++ } else { ++ ast_log(LOG_NOTICE, "Couldn't find dnd or available node?\n"); ++ res = -1; ++ goto presence_publish_cleanup; ++ } ++ ++ if (!(peer = sip_find_peer(pvt->peername, NULL, TRUE, FINDALLDEVICES, FALSE, 0))) { ++ ast_log(LOG_NOTICE, "No such peer '%s'\n", pvt->peername); ++ res = -1; ++ goto presence_publish_cleanup; ++ } ++ ++ if (peer->donotdisturb != donotdisturb) { ++ peer->donotdisturb = donotdisturb; ++ ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", peer->name); ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (alias->peer) { ++ alias->peer->donotdisturb = peer->donotdisturb; ++ ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", alias->peer->name); ++ } ++ } ++ if (!peer->is_realtime) { ++ ast_db_put("SIP/DoNotDisturb", peer->name, peer->donotdisturb ? "yes" : "no"); ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "donotdisturb", donotdisturb ? "yes" : "no", SENTINEL); ++ } ++ } ++ ++ sip_unref_peer(peer, "presence_esc_publish_handler: from sip_find_peer call, setting DoNotDisturb/Available"); ++ } else { ++ res = -1; ++ } ++ ++presence_publish_cleanup: ++ if (pidf_doc) { ++ ast_xml_close(pidf_doc); ++ } ++ if (res) { ++ transmit_response(pvt, "400 Bad Request", req); ++ } ++ return res; ++} + #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) +@@ -28591,6 +32553,182 @@ + return 0; + } + ++enum { ++ FE_BULKUPDATE, ++ FE_DONOTDISTURB, ++ FE_CALLFORWARD ++}; ++ ++/*! \brief Handle incoming feature event SUBSCRIBE body */ ++static int handle_subscribe_featureevent(struct sip_peer *peer, struct sip_request *req, int *feature) ++{ ++#ifdef HAVE_LIBXML2 ++ const char *content_type = sip_get_header(req, "Content-Type"); ++ char *featureevent_body; ++ struct ast_xml_doc *featureevent_doc = NULL; ++ struct ast_xml_node *root_node; ++ ++ if (!atoi(sip_get_header(req, "Content-Length"))) { ++ /* Peer is subscribing to the current DoNotDisturb and CallForward state */ ++ *feature = FE_BULKUPDATE; ++ return 0; ++ } ++ ++ if (strcasecmp(content_type, "application/x-as-feature-event+xml")) { ++ ast_log(LOG_WARNING, "Content type is not x-as-feature-event+xml\n"); ++ return -1; ++ } ++ ++ if (!(featureevent_body = get_content(req, 0, req->lines - 1))) { ++ ast_log(LOG_WARNING, "Unable to get feature event body\n"); ++ return -1; ++ } ++ ++ if (!(featureevent_doc = ast_xml_read_memory(featureevent_body, strlen(featureevent_body)))) { ++ ast_log(LOG_WARNING, "Unable to open XML as-feature-event document. Is it malformed?\n"); ++ return -1; ++ } ++ ++ if (!(root_node = ast_xml_get_root(featureevent_doc))) { ++ ast_log(LOG_WARNING, "Unable to get root node\n"); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ if (!strcmp(ast_xml_node_get_name(root_node), "SetDoNotDisturb")) { ++ int donotdisturb; ++ struct ast_xml_node *set_donotdisturb_node, *set_donotdisturb_children; ++ struct ast_xml_node *donotdisturb_on_node; ++ const char *donotdisturb_on_text; ++ ++ set_donotdisturb_node = root_node; ++ ++ if (!(set_donotdisturb_children = ast_xml_node_get_children(set_donotdisturb_node))) { ++ ast_log(LOG_WARNING, "No tuples within SetDoNotDisturb node"); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ if (!(donotdisturb_on_node = ast_xml_find_element(set_donotdisturb_children, "doNotDisturbOn", NULL, NULL))) { ++ ast_log(LOG_WARNING, "Couldn't find doNotDisturbOn node"); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ donotdisturb_on_text = ast_xml_get_text(donotdisturb_on_node); ++ ++ if (!strcmp(donotdisturb_on_text, "true")) { ++ donotdisturb = 1; ++ } else if (!strcmp(donotdisturb_on_text, "false")) { ++ donotdisturb = 0; ++ } else { ++ ast_log(LOG_WARNING, "Invalid content in doNotDisturbOn node %s\n", donotdisturb_on_text); ++ ast_xml_free_text(donotdisturb_on_text); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ ast_xml_free_text(donotdisturb_on_text); ++ ++ if (peer->donotdisturb != donotdisturb) { ++ peer->donotdisturb = donotdisturb; ++ ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", peer->name); ++ if (!peer->is_realtime) { ++ ast_db_put("SIP/DoNotDisturb", peer->name, peer->donotdisturb ? "yes" : "no"); ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "donotdisturb", donotdisturb ? "yes" : "no", SENTINEL); ++ } ++ } ++ ++ *feature = FE_DONOTDISTURB; ++ } else if (!strcmp(ast_xml_node_get_name(root_node), "SetForwarding")) { ++ char callforward[AST_MAX_EXTENSION]; ++ struct ast_xml_node *set_forwarding_node, *set_forwarding_children; ++ struct ast_xml_node *forwarding_type_node, *activate_forward_node, *forward_dn_node; ++ const char *forwarding_type_text, *activate_forward_text, *forward_dn_text; ++ ++ set_forwarding_node = root_node; ++ ++ if (!(set_forwarding_children = ast_xml_node_get_children(set_forwarding_node))) { ++ ast_log(LOG_WARNING, "No tuples within SetForwarding node"); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ if (!(forwarding_type_node = ast_xml_find_element(set_forwarding_children, "forwardingType", NULL, NULL))) { ++ ast_log(LOG_WARNING, "Couldn't find forwardingType node\n"); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ forwarding_type_text = ast_xml_get_text(forwarding_type_node); ++ ++ if (strcmp(forwarding_type_text, "forwardImmediate")) { ++ ast_log(LOG_WARNING, "forwardingType not supported: %s\n", forwarding_type_text); ++ ast_xml_free_text(forwarding_type_text); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ ast_xml_free_text(forwarding_type_text); ++ ++ if (!(activate_forward_node = ast_xml_find_element(set_forwarding_children, "activateForward", NULL, NULL))) { ++ ast_log(LOG_WARNING, "Couldn't find activateForward node"); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ activate_forward_text = ast_xml_get_text(activate_forward_node); ++ ++ if (!strcmp(activate_forward_text, "true")) { ++ if (!(forward_dn_node = ast_xml_find_element(set_forwarding_children, "forwardDN", NULL, NULL))) { ++ ast_log(LOG_WARNING, "Couldn't find forwardDN node\n"); ++ ast_xml_free_text(activate_forward_text); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ forward_dn_text = ast_xml_get_text(forward_dn_node); ++ ast_copy_string(callforward, S_OR(forward_dn_text, ""), sizeof(callforward)); ++ ast_xml_free_text(forward_dn_text); ++ } else if (!strcmp(activate_forward_text, "false")) { ++ callforward[0] = '\0'; ++ } else { ++ ast_log(LOG_WARNING, "Invalid content in activateForward node: %s\n", activate_forward_text); ++ ast_xml_free_text(activate_forward_text); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ ast_xml_free_text(activate_forward_text); ++ ++ if (strcmp(peer->callforward, callforward)) { ++ ast_string_field_set(peer, callforward, callforward); ++ if (!peer->is_realtime) { ++ if (ast_strlen_zero(peer->callforward)) { ++ ast_db_del("SIP/CallForward", peer->name); ++ } else { ++ ast_db_put("SIP/CallForward", peer->name, peer->callforward); ++ } ++ } else if (sip_cfg.peer_rtupdate && ast_check_realtime("sippeers")) { ++ ast_update_realtime("sippeers", "name", peer->name, "callforward", peer->callforward, SENTINEL); ++ } ++ } ++ ++ *feature = FE_CALLFORWARD; ++ } else { ++ ast_log(LOG_WARNING, "Couldn't find SetDoNotDisturb or SetForwarding node: %s\n", ast_xml_node_get_name(root_node)); ++ ast_xml_close(featureevent_doc); ++ return -1; ++ } ++ ++ ast_xml_close(featureevent_doc); ++ return 0; ++#else ++ return -1 ++#endif ++} ++ + /*! \brief Handle incoming SUBSCRIBE request */ + static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, const char *e) + { +@@ -28599,6 +32737,7 @@ + char *event = ast_strdupa(sip_get_header(req, "Event")); /* Get Event package name */ + int resubscribe = (p->subscribed != NONE) && !req->ignore; + char *options; ++ int feature = -1; + + if (p->initreq.headers) { + /* We already have a dialog */ +@@ -28732,12 +32871,19 @@ + subscribed = XPIDF_XML; /* Older versions of Polycom firmware will claim pidf+xml, but really they only support xpidf+xml */ + } else { + subscribed = PIDF_XML; /* RFC 3863 format */ ++ if (strstr(p->useragent, "Digium")) { ++ ast_set_flag(&p->flags[2], SIP_PAGE3_WHY_DIDNT_DIGIUM_THINK_OF_THIS); ++ } + } + } else if (strstr(accept, "application/dialog-info+xml")) { + subscribed = DIALOG_INFO_XML; + /* IETF draft: draft-ietf-sipping-dialog-package-05.txt */ + } else if (strstr(accept, "application/cpim-pidf+xml")) { +- subscribed = CPIM_PIDF_XML; /* RFC 3863 format */ ++ if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ subscribed = PIDF_XML; /* RFC 3863 format + Cisco USECALLMANAGER */ ++ } else { ++ subscribed = CPIM_PIDF_XML; /* RFC 3863 format */ ++ } + } else if (strstr(accept, "application/xpidf+xml")) { + subscribed = XPIDF_XML; /* Early pre-RFC 3863 format with MSN additions (Microsoft Messenger) */ + } else { +@@ -28860,6 +33006,33 @@ + p->relatedpeer = sip_ref_peer(authpeer, "setting dialog's relatedpeer pointer"); + } + /* Do not release authpeer here */ ++ } else if (!strcmp(event, "as-feature-event")) { ++ if (!authpeer || handle_subscribe_featureevent(authpeer, req, &feature) == -1) { ++ transmit_response(p, "489 Bad Event", req); ++ pvt_set_needdestroy(p, "unknown format"); ++ if (authpeer) { ++ sip_unref_peer(authpeer, "sip_unref_peer, from handle_request_subscribe (authpeer 4)"); ++ } ++ return 0; ++ } ++ ++ p->subscribed = FEATURE_EVENTS; ++ if (authpeer->fepvt != p) { /* Destroy old PVT if this is a new one */ ++ /* We only allow one subscription per peer */ ++ if (authpeer->fepvt) { ++ dialog_unlink_all(authpeer->fepvt); ++ authpeer->fepvt = dialog_unref(authpeer->fepvt, "unref dialog authpeer->fepvt"); ++ } ++ authpeer->fepvt = dialog_ref(p, "setting peers' fepvt to p"); ++ } ++ ++ if (p->relatedpeer != authpeer) { ++ if (p->relatedpeer) { ++ sip_unref_peer(p->relatedpeer, "Unref previously stored relatedpeer ptr"); ++ } ++ p->relatedpeer = sip_ref_peer(authpeer, "setting dialog's relatedpeer pointer"); ++ } ++ /* 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 */ +@@ -28904,6 +33077,9 @@ + if (p->subscribed == MWI_NOTIFICATION && p->relatedpeer) { + ast_debug(2, "%s subscription for mailbox notification - peer %s\n", + action, p->relatedpeer->name); ++ } else if (p->subscribed == FEATURE_EVENTS) { ++ ast_debug(2, "%s feature event subscription for peer %s\n", ++ action, p->username); + } else if (p->subscribed == CALL_COMPLETION) { + ast_debug(2, "%s CC subscription for peer %s\n", action, p->username); + } else { +@@ -28926,10 +33102,38 @@ + struct sip_peer *peer = p->relatedpeer; + sip_ref_peer(peer, "ensure a peer ref is held during MWI sending"); + ao2_unlock(p); +- sip_send_mwi_to_peer(peer, 0); ++ sip_send_mwi(peer, 0); + ao2_lock(p); + sip_unref_peer(peer, "release a peer ref now that MWI is sent"); + } ++ } else if (p->subscribed == FEATURE_EVENTS) { ++ struct sip_request resp; ++ ++ ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); ++ respprep(&resp, p, "200 OK", req); ++ if (feature != FE_BULKUPDATE) { ++ add_header(&resp, "Content-Type", "application/x-as-feature-event+xml"); ++ add_content(&resp, "\n"); ++ if (feature == FE_DONOTDISTURB) { ++ add_content(&resp, "\n"); ++ } else if (feature == FE_CALLFORWARD) { ++ add_content(&resp, "\n"); ++ } ++ } ++ send_response(p, &resp, XMIT_UNRELIABLE, 0); ++ ++ if (p->relatedpeer) { ++ struct sip_peer *peer = p->relatedpeer; ++ sip_ref_peer(peer, "ensure a peer ref is held during feature events sending"); ++ if (feature == FE_BULKUPDATE) { ++ sip_send_bulkupdate(peer); ++ } else if (feature == FE_DONOTDISTURB) { ++ sip_send_donotdisturb(peer); ++ } else if (feature == FE_CALLFORWARD) { ++ sip_send_callforward(peer); ++ } ++ sip_unref_peer(peer, "release a peer ref now that feature events is sent"); ++ } + } else if (p->subscribed != CALL_COMPLETION) { + struct state_notify_data data = { 0, }; + char *subtype = NULL; +@@ -28950,6 +33154,9 @@ + + sip_pvt_unlock(p); + data.state = ast_extension_state_extended(NULL, p->context, p->exten, &device_state_info); ++ data.presence_state = ast_hint_presence_state(NULL, p->context, p->exten, &subtype, &message); ++ data.presence_subtype = subtype; ++ data.presence_message = message; + sip_pvt_lock(p); + + if (data.state < 0) { +@@ -28964,11 +33171,7 @@ + } + return 0; + } +- if (allow_notify_user_presence(p)) { +- data.presence_state = ast_hint_presence_state(NULL, p->context, p->exten, &subtype, &message); +- data.presence_subtype = subtype; +- data.presence_message = message; +- } ++ + ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); + transmit_response(p, "200 OK", req); + /* RFC 3265: A notification must be sent on every subscribe, so force it */ +@@ -29315,7 +33518,7 @@ + + break; + case SIP_REFER: +- res = handle_request_refer(p, req, seqno, nounlock); ++ res = handle_request_refer(p, req, addr, seqno, e, nounlock); + break; + case SIP_CANCEL: + res = handle_request_cancel(p, req); +@@ -29775,7 +33978,7 @@ + * \retval -1 on failure. + * \retval 0 on success. + */ +-static int sip_send_mwi_to_peer(struct sip_peer *peer, int cache_only) ++static int sip_send_mwi(struct sip_peer *peer, int cache_only) + { + /* Called with peer lock, but releases it */ + struct sip_pvt *p; +@@ -29805,7 +34008,7 @@ + if (!get_cached_mwi(peer, &newmsgs, &oldmsgs) && !cache_only) { + /* Fall back to manually checking the mailbox if not cache_only and get_cached_mwi failed */ + struct ast_str *mailbox_str = ast_str_alloca(512); +- peer_mailboxes_to_str(&mailbox_str, peer); ++ get_peer_mailboxes(&mailbox_str, peer); + /* if there is no mailbox do nothing */ + if (!ast_str_strlen(mailbox_str)) { + ao2_unlock(peer); +@@ -29823,7 +34026,7 @@ + + if (peer->mwipvt) { + /* Base message on subscription */ +- p = dialog_ref(peer->mwipvt, "sip_send_mwi_to_peer: Setting dialog ptr p from peer->mwipvt"); ++ p = dialog_ref(peer->mwipvt, "sip_send_mwi: Setting dialog ptr p from peer->mwipvt"); + ao2_unlock(peer); + } else { + ao2_unlock(peer); +@@ -29873,13 +34076,360 @@ + /* the following will decrement the refcount on p as it finishes */ + transmit_notify_with_mwi(p, newmsgs, oldmsgs, vmexten); + sip_pvt_unlock(p); +- dialog_unref(p, "unref dialog ptr p just before it goes out of scope at the end of sip_send_mwi_to_peer."); ++ dialog_unref(p, "unref dialog ptr p just before it goes out of scope at the end of sip_send_mwi."); + + update_peer_lastmsgssent(peer, ((newmsgs > 0x7fff ? 0x7fff0000 : (newmsgs << 16)) | (oldmsgs > 0xffff ? 0xffff : oldmsgs)), 0); + + return 0; + } + ++/*! ++ * \brief Expire bulk-register aliases ++ */ ++static void expire_peer_aliases(struct sip_peer *peer) ++{ ++ struct sip_alias *alias; ++ ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (!alias->peer) { ++ continue; ++ } ++ ++ ast_verb(3, "Unregistered SIP '%s'\n", alias->name); ++ alias->peer->lastms = 0; ++ memset(&alias->peer->addr, 0, sizeof(alias->peer->addr)); ++ ++ if (alias->peer->socket.tcptls_session) { ++ ao2_ref(alias->peer->socket.tcptls_session, -1); ++ alias->peer->socket.tcptls_session = NULL; ++ } else if (alias->peer->socket.ws_session) { ++ ast_websocket_unref(alias->peer->socket.ws_session); ++ alias->peer->socket.ws_session = NULL; ++ } ++ ++ ast_string_field_set(alias->peer, fullcontact, ""); ++ ast_string_field_set(alias->peer, username, ""); ++ ast_string_field_set(alias->peer, useragent, ""); ++ ++ if (alias->peer->endpoint) { ++ RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); ++ ast_endpoint_set_state(alias->peer->endpoint, AST_ENDPOINT_OFFLINE); ++ blob = ast_json_pack("{s: s, s: s}", ++ "peer_status", "Unregistered", ++ "cause", "Expired"); ++ ast_endpoint_blob_publish(alias->peer->endpoint, ast_endpoint_state_type(), blob); ++ } ++ ++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); ++ register_peer_exten(alias->peer, 0); ++ ++ sip_unref_peer(alias->peer, "unref after sip_find_peer"); ++ alias->peer = NULL; ++ } ++} ++ ++/*! ++ * \brief Update bulk-register aliases ++ */ ++static void register_peer_aliases(struct sip_peer *peer) ++{ ++ struct sip_alias *alias; ++ char *scheme, *hostport; ++ ++ if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { ++ return; ++ } ++ ++ scheme = ast_strdupa(peer->fullcontact); ++ if ((hostport = strchr(scheme, ':')) != NULL) { ++ *hostport++ = '\0'; ++ if ((hostport = strchr(hostport, '@')) != NULL) { ++ *hostport++ = '\0'; ++ } ++ } ++ ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (!alias->peer) { ++ if (!(alias->peer = sip_find_peer(alias->name, NULL, TRUE, FINDPEERS, FALSE, 0))) { ++ ast_log(LOG_WARNING, "No such register peer '%s'\n", alias->name); ++ continue; ++ } ++ ++ /* remove any schedules that may have been created */ ++ peer_sched_cleanup(alias->peer); ++ } ++ ++ /* These settings could have been overwritten by a reload */ ++ alias->peer->cisco_lineindex = alias->lineindex; ++ ast_string_field_set(alias->peer, username, alias->name); ++ ++ ast_string_field_set(alias->peer, cisco_authname, peer->name); ++ ast_string_field_set(alias->peer, secret, peer->secret); ++ ast_string_field_set(alias->peer, md5secret, peer->md5secret); ++ ++ ast_string_field_set(alias->peer, regcallid, peer->regcallid); ++ ast_string_field_set(alias->peer, cisco_devicename, peer->cisco_devicename); ++ ast_string_field_set(alias->peer, useragent, peer->useragent); ++ ++ alias->peer->donotdisturb = peer->donotdisturb; ++ alias->peer->huntgroup = peer->huntgroup; ++ ++ /* Peer hasn't changed */ ++ if (!ast_sockaddr_cmp(&peer->addr, &alias->peer->addr)) { ++ continue; ++ } ++ ++ alias->peer->portinuri = peer->portinuri; ++ alias->peer->fromdomainport = peer->fromdomainport; ++ alias->peer->sipoptions = peer->sipoptions; ++ ++ ast_string_field_build(alias->peer, fullcontact, "%s:%s@%s", scheme, alias->peer->name, hostport); ++ ++ alias->peer->addr = peer->addr; ++ copy_socket_data(&alias->peer->socket, &peer->socket); ++ ++ if (alias->peer->endpoint) { ++ RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); ++ ast_endpoint_set_state(alias->peer->endpoint, AST_ENDPOINT_ONLINE); ++ blob = ast_json_pack("{s: s, s: s}", ++ "peer_status", "Registered", ++ "address", ast_sockaddr_stringify(&peer->addr)); ++ ast_endpoint_blob_publish(alias->peer->endpoint, ast_endpoint_state_type(), blob); ++ } ++ ++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); ++ register_peer_exten(alias->peer, 1); ++ ++ ast_verb(3, "Registered SIP '%s' at %s\n", alias->peer->name, ast_sockaddr_stringify(&alias->peer->addr)); ++ alias->peer->offhook = 0; ++ ++ update_peer(alias->peer, max_expiry); ++ update_peer_lastmsgssent(alias->peer, -1, 0); ++ } ++} ++ ++/*! ++ * \brief Register all bulk-register aliases ++ */ ++static void register_all_aliases(void) ++{ ++ struct ao2_iterator i; ++ struct sip_peer *peer; ++ struct sip_alias *alias; ++ ++ if (!speerobjs) { ++ return; ++ } ++ ++ i = ao2_iterator_init(peers, 0); ++ while ((peer = ao2_t_iterator_next(&i, "iterate thru peers table"))) { ++ ao2_lock(peer); ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (alias->peer) { ++ alias->peer->addr = peer->addr; ++ } ++ } ++ register_peer_aliases(peer); ++ ao2_unlock(peer); ++ sip_unref_peer(peer, "toss iterator peer ptr"); ++ } ++ ao2_iterator_destroy(&i); ++} ++ ++/*! ++ * \brief Send donotdisturb, call forward and huntgroup in one bulk update ++ */ ++static int sip_send_bulkupdate(struct sip_peer *peer) ++{ ++ if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { ++ return 0; ++ } ++ ++ if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ struct sip_pvt *pvt; ++ struct ast_str *content; ++ int newmsgs = 0, oldmsgs = 0; ++ struct sip_alias *alias; ++ ++ if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ return -1; ++ } ++ ++ if (!(content = ast_str_create(8192))) { ++ dialog_unref(pvt, "drop pvt"); ++ return -1; ++ } ++ ++ /* Don't use create_addr_from_peer here as it may fail due to the peer not having responded to an OPTIONS request yet */ ++ pvt->sa = peer->addr; ++ pvt->recv = peer->addr; ++ copy_socket_data(&pvt->socket, &peer->socket); ++ ast_copy_flags(&pvt->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY); ++ ast_copy_flags(&pvt->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY); ++ ast_copy_flags(&pvt->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY); ++ ++ /* Recalculate our side, and recalculate Call ID */ ++ ast_sip_ouraddrfor(&pvt->sa, &pvt->ourip, pvt); ++ change_callid_pvt(pvt, NULL); ++ ++ if (!ast_strlen_zero(peer->tohost)) { ++ ast_string_field_set(pvt, tohost, peer->tohost); ++ } else { ++ ast_string_field_set(pvt, tohost, ast_sockaddr_stringify_host_remote(&peer->addr)); ++ } ++ if (!pvt->portinuri) { ++ pvt->portinuri = peer->portinuri; ++ } ++ if (peer->fromdomainport) { ++ pvt->fromdomainport = peer->fromdomainport; ++ } ++ ++ ast_string_field_set(pvt, fullcontact, peer->fullcontact); ++ ast_string_field_set(pvt, username, peer->username); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", peer->donotdisturb ? "enable" : "disable"); ++ ast_str_append(&content, 0, "\n", ast_test_flag(&peer->flags[2], SIP_PAGE3_DND_BUSY) ? "callreject" : "ringeroff"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", peer->huntgroup ? "on" : "off"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 9, "\n"); ++ ++ if (!get_cached_mwi(peer, &newmsgs, &oldmsgs)) { ++ struct ast_str *mailbox = ast_str_alloca(512); ++ ++ get_peer_mailboxes(&mailbox, peer); ++ if (ast_str_strlen(mailbox)) { ++ ast_app_inboxcount(ast_str_buffer(mailbox), &newmsgs, &oldmsgs); ++ update_peer_lastmsgssent(peer, ((newmsgs > 0x7fff ? 0x7fff0000 : (newmsgs << 16)) | (oldmsgs > 0xffff ? 0xffff : oldmsgs)), 0); ++ } else { ++ update_peer_lastmsgssent(peer, -1, 0); ++ } ++ } ++ ++ ast_str_append(&content, 0, "\n", peer->cisco_lineindex); ++ ast_str_append(&content, 0, "%s\n", newmsgs ? "yes" : "no"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n", newmsgs, oldmsgs); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", peer->callforward); ++ ast_str_append(&content, 0, "%s\n", !ast_strlen_zero(peer->vmexten) && !strcmp(peer->callforward, peer->vmexten) ? "on" : "off"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ int newmsgs = 0, oldmsgs = 0; ++ ++ if (!alias->peer) { ++ continue; ++ } ++ ++ if (!get_cached_mwi(alias->peer, &newmsgs, &oldmsgs)) { ++ struct ast_str *mailbox = ast_str_alloca(512); ++ ++ get_peer_mailboxes(&mailbox, alias->peer); ++ if (ast_str_strlen(mailbox)) { ++ ast_app_inboxcount(ast_str_buffer(mailbox), &newmsgs, &oldmsgs); ++ update_peer_lastmsgssent(peer, ((newmsgs > 0x7fff ? 0x7fff0000 : (newmsgs << 16)) | (oldmsgs > 0xffff ? 0xffff : oldmsgs)), 0); ++ } else { ++ update_peer_lastmsgssent(peer, -1, 0); ++ } ++ } ++ ++ ast_str_append(&content, 0, "\n", alias->peer->cisco_lineindex); ++ ast_str_append(&content, 0, "%s\n", newmsgs ? "yes" : "no"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n", newmsgs, oldmsgs); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "%s\n", alias->peer->callforward); ++ ast_str_append(&content, 0, "%s\n", !ast_strlen_zero(alias->peer->vmexten) && !strcmp(alias->peer->callforward, alias->peer->vmexten) ? "on" : "off"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ } ++ ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(pvt, "bump down the count of pvt since we're done with it."); ++ ++ ast_free(content); ++ } else if (peer->fepvt) { ++ struct sip_request req; ++ struct sip_pvt *pvt = peer->fepvt; ++ char tmp[512], boundary[32]; ++ ++ ast_set_flag(&pvt->flags[0], SIP_OUTGOING); ++ reqprep(&req, pvt, SIP_NOTIFY, 0, 1); ++ ++ snprintf(boundary, sizeof(boundary), "%08lx%08lx%08lx", ast_random(), ast_random(), ast_random()); ++ ++ add_header(&req, "Event", "as-feature-event"); ++ if (pvt->expiry) { ++ add_header(&req, "Subscription-State", "active"); ++ } else { ++ add_header(&req, "Subscription-State", "terminated;reason=timeout"); ++ } ++ snprintf(tmp, sizeof(tmp), "multipart/mixed; boundary=%s", boundary); ++ add_header(&req, "Content-Type", tmp); ++ ++ snprintf(tmp, sizeof(tmp), "--%s\r\n", boundary); ++ add_content(&req, tmp); ++ add_content(&req, "Content-Type: application/x-as-feature-event+xml\r\n"); ++ add_content(&req, "\r\n"); ++ add_content(&req, "\n"); ++ add_content(&req, "\n"); ++ snprintf(tmp, sizeof(tmp), "\n%s\n", peer->donotdisturb ? "true" : "false"); ++ add_content(&req, tmp); ++ add_content(&req, "\n"); ++ add_content(&req, "\r\n"); ++ ++ snprintf(tmp, sizeof(tmp), "--%s\r\n", boundary); ++ add_content(&req, tmp); ++ add_content(&req, "Content-Type: application/x-as-feature-event+xml\r\n"); ++ add_content(&req, "\r\n"); ++ add_content(&req, "\n"); ++ add_content(&req, "\n"); ++ add_content(&req, "\nforwardImmediate\n"); ++ snprintf(tmp, sizeof(tmp), "%s\n%s\n", !ast_strlen_zero(peer->callforward) ? "true" : "false", peer->callforward); ++ add_content(&req, tmp); ++ add_content(&req, "\n"); ++ add_content(&req, "\r\n"); ++ ++ snprintf(tmp, sizeof(tmp), "--%s--\r\n", boundary); ++ add_content(&req, tmp); ++ send_request(pvt, &req, XMIT_RELIABLE, pvt->ocseq); ++ } ++ ++ return 0; ++} ++ + static struct ast_manager_event_blob *session_timeout_to_ami(struct stasis_message *msg) + { + RAII_VAR(struct ast_str *, channel_string, NULL, ast_free); +@@ -30581,6 +35131,25 @@ + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); + } + ++ if (!AST_LIST_EMPTY(&peer->aliases)) { ++ struct sip_alias *alias; ++ ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (!alias->peer || alias->peer->lastms == -1) { ++ continue; ++ } ++ ++ ast_log(LOG_NOTICE, "Peer '%s' is now UNREACHABLE! Last qualify: %d\n", alias->peer->name, alias->peer->lastms); ++ manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Unreachable\r\nTime: %d\r\n", alias->peer->name, -1); ++ ++ alias->peer->lastms = -1; ++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); ++ if (sip_cfg.regextenonqualify) { ++ register_peer_exten(alias->peer, FALSE); ++ } ++ } ++ } ++ + /* Try again quickly */ + AST_SCHED_REPLACE_UNREF(peer->pokeexpire, sched, + DEFAULT_FREQ_NOTOK, sip_poke_peer_s, peer, +@@ -30734,13 +35303,13 @@ + char *host; + char *tmp; + struct sip_peer *p; +- + int res = AST_DEVICE_INVALID; + + /* make sure data is not null. Maybe unnecessary, but better be safe */ + host = ast_strdupa(data ? data : ""); +- if ((tmp = strchr(host, '@'))) ++ if ((tmp = strchr(host, '@'))) { + host = tmp + 1; ++ } + + ast_debug(3, "Checking device state for peer %s\n", host); + +@@ -30778,7 +35347,7 @@ + else if (p->call_limit && p->busy_level && p->inuse >= p->busy_level) + /* We're forcing busy before we've reached the call limit */ + res = AST_DEVICE_BUSY; +- else if (p->call_limit && p->inuse) ++ else if (p->call_limit && (p->inuse || p->offhook)) + /* Not busy, but we do have a call */ + res = AST_DEVICE_INUSE; + else if (p->maxms && ((p->lastms > p->maxms) || (p->lastms < 0))) +@@ -30796,6 +35365,35 @@ + return res; + } + ++static int sip_presencestate(const char *data, char **subtype, char **message) ++{ ++ char *host; ++ char *tmp; ++ struct sip_peer *p; ++ int res = AST_PRESENCE_INVALID; ++ ++ /* make sure data is not null. Maybe unnecessary, but better be safe */ ++ host = ast_strdupa(data ? data : ""); ++ if ((tmp = strchr(host, '@'))) { ++ host = tmp + 1; ++ } ++ ++ ast_debug(3, "Checking presence state for peer %s\n", host); ++ ++ if ((p = sip_find_peer(host, NULL, FALSE, FINDALLDEVICES, TRUE, 0))) { ++ if (!(ast_sockaddr_isnull(&p->addr) && ast_sockaddr_isnull(&p->defaddr))) { ++ if (p->donotdisturb) { ++ res = AST_PRESENCE_DND; ++ } else { ++ res = AST_PRESENCE_AVAILABLE; ++ } ++ } ++ sip_unref_peer(p, "sip_unref_peer, from sip_presencestate, release ref from sip_find_peer"); ++ } ++ ++ return res; ++} ++ + /*! \brief PBX interface function -build SIP pvt structure + * SIP calls initiated by the PBX arrive here. + * +@@ -31290,9 +35888,21 @@ + } else if (!strcasecmp(v->name, "rfc2833compensate")) { + ast_set_flag(&mask[1], SIP_PAGE2_RFC2833_COMPENSATE); + ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_RFC2833_COMPENSATE); +- } else if (!strcasecmp(v->name, "buggymwi")) { +- ast_set_flag(&mask[1], SIP_PAGE2_BUGGY_MWI); +- ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_BUGGY_MWI); ++ } else if (!strcasecmp(v->name, "dndbusy")) { ++ ast_set_flag(&mask[2], SIP_PAGE3_DND_BUSY); ++ ast_set2_flag(&flags[2], ast_true(v->value), SIP_PAGE3_DND_BUSY); ++ } else if (!strcasecmp(v->name, "huntgroup_default")) { ++ ast_set_flag(&mask[2], SIP_PAGE3_HUNTGROUP_DEFAULT); ++ ast_set2_flag(&flags[2], ast_true(v->value), SIP_PAGE3_HUNTGROUP_DEFAULT); ++ } else if (!strcasecmp(v->name, "cisco_usecallmanager")) { ++ ast_set_flag(&mask[1], SIP_PAGE2_CISCO_USECALLMANAGER); ++ ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_CISCO_USECALLMANAGER); ++ } else if (!strcasecmp(v->name, "cisco_keep_conference")) { ++ ast_set_flag(&mask[2], SIP_PAGE3_CISCO_KEEP_CONFERENCE); ++ ast_set2_flag(&flags[2], ast_true(v->value), SIP_PAGE3_CISCO_KEEP_CONFERENCE); ++ } else if (!strcasecmp(v->name, "cisco_multiadmin_conference")) { ++ ast_set_flag(&mask[2], SIP_PAGE3_CISCO_MULTIADMIN_CONFERENCE); ++ ast_set2_flag(&flags[2], ast_true(v->value), SIP_PAGE3_CISCO_MULTIADMIN_CONFERENCE); + } else if (!strcasecmp(v->name, "rtcp_mux")) { + ast_set_flag(&mask[2], SIP_PAGE3_RTCP_MUX); + ast_set2_flag(&flags[2], ast_true(v->value), SIP_PAGE3_RTCP_MUX); +@@ -31554,7 +36164,6 @@ + peer->stimer.st_max_se = global_max_se; + peer->timer_t1 = global_t1; + peer->timer_b = global_timer_b; +- clear_peer_mailboxes(peer); + peer->disallowed_methods = sip_cfg.disallowed_methods; + peer->transports = default_transports; + peer->default_outbound_transport = default_primary_transport; +@@ -31562,6 +36171,8 @@ + ao2_ref(peer->outboundproxy, -1); + peer->outboundproxy = NULL; + } ++ peer->cisco_lineindex = 1; ++ peer->cisco_pickupnotify_timer = 5; + } + + /*! \brief Create temporary peer (used in autocreatepeer mode) */ +@@ -31613,7 +36224,6 @@ + + while ((mbox = strsep(&next, ","))) { + struct sip_mailbox *mailbox; +- int duplicate = 0; + + /* remove leading/trailing whitespace from mailbox string */ + mbox = ast_strip(mbox); +@@ -31624,12 +36234,11 @@ + /* Check whether the mailbox is already in the list */ + AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, entry) { + if (!strcmp(mailbox->id, mbox)) { +- duplicate = 1; +- mailbox->status = SIP_MAILBOX_STATUS_EXISTING; + break; + } + } +- if (duplicate) { ++ if (mailbox) { ++ mailbox->status = SIP_MAILBOX_STATUS_EXISTING; + continue; + } + +@@ -31645,6 +36254,87 @@ + } + } + ++static void add_peer_alias(struct sip_peer *peer, const char *value, int *lineindex) ++{ ++ char *next, *name; ++ struct sip_alias *alias; ++ ++ next = ast_strdupa(value); ++ ++ while ((name = strsep(&next, ","))) { ++ name = ast_strip(name); ++ ++ if (ast_strlen_zero(name)) { ++ continue; ++ } ++ ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ if (!strcmp(alias->name, name)) { ++ break; ++ } ++ } ++ ++ if (alias) { ++ alias->removed = 0; ++ } else { ++ if (!(alias = ast_calloc(1, sizeof(*alias)))) { ++ return; ++ } ++ ++ if (!(alias->name = ast_strdup(name))) { ++ ast_free(alias); ++ return; ++ } ++ ++ AST_LIST_INSERT_TAIL(&peer->aliases, alias, entry); ++ } ++ ++ alias->lineindex = (*lineindex)++; ++ } ++} ++ ++static void add_peer_subscription(struct sip_peer *peer, const char *value) ++{ ++ char *next, *exten, *context; ++ struct sip_subscription *subscription; ++ ++ next = ast_strdupa(value); ++ ++ while ((exten = strsep(&next, ","))) { ++ if ((context = strchr(exten, '@'))) { ++ *context++ = '\0'; ++ context = ast_strip(context); ++ } else { ++ context = ast_strdupa(S_OR(peer->subscribecontext, peer->context)); ++ } ++ ++ exten = ast_strip(exten); ++ ++ if (ast_strlen_zero(exten) || ast_strlen_zero(context)) { ++ continue; ++ } ++ ++ AST_LIST_TRAVERSE(&peer->subscriptions, subscription, entry) { ++ if (!strcmp(subscription->exten, exten) && !strcmp(subscription->context, context)) { ++ break; ++ } ++ } ++ ++ if (subscription) { ++ subscription->removed = 0; ++ } else { ++ if (!(subscription = ast_calloc_with_stringfields(1, struct sip_subscription, 32))) { ++ return; ++ } ++ ++ ast_string_field_set(subscription, exten, exten); ++ ast_string_field_set(subscription, context, context); ++ ++ AST_LIST_INSERT_TAIL(&peer->subscriptions, subscription, entry); ++ } ++ } ++} ++ + /*! \brief Build peer from configuration (file or realtime static/dynamic) */ + static struct sip_peer *build_peer(const char *name, struct ast_variable *v_head, struct ast_variable *alt, int realtime, int devstate_only) + { +@@ -31668,6 +36358,7 @@ + int alt_fullcontact = alt ? 1 : 0, headercount = 0; + struct ast_str *fullcontact = ast_str_alloca(512); + int acl_change_subscription_needed = 0; ++ int lineindex = 2; + + if (!realtime || ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS)) { + /* Note we do NOT use sip_find_peer here, to avoid realtime recursion */ +@@ -31765,9 +36456,18 @@ + + if (!devstate_only) { + struct sip_mailbox *mailbox; ++ struct sip_alias *alias; ++ struct sip_subscription *subscription; ++ + AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, entry) { + mailbox->status = SIP_MAILBOX_STATUS_UNKNOWN; + } ++ AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { ++ alias->removed = 1; ++ } ++ AST_LIST_TRAVERSE(&peer->subscriptions, subscription, entry) { ++ subscription->removed = 1; ++ } + } + + /* clear named callgroup and named pickup group container */ +@@ -31982,7 +36682,7 @@ + } else if (!strcasecmp(v->name, "regexten")) { + ast_string_field_set(peer, regexten, v->value); + } else if (!strcasecmp(v->name, "callbackextension")) { +- ast_string_field_set(peer, callback, v->value); ++ ast_string_field_set(peer, callbackexten, v->value); + } else if (!strcasecmp(v->name, "amaflags")) { + format = ast_channel_string2amaflag(v->value); + if (format < 0) { +@@ -32152,6 +36852,46 @@ + ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL); + } else if (!strcasecmp(v->name, "force_avp")) { + ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_FORCE_AVP); ++ } else if (!strcasecmp(v->name, "register")) { ++ if (!strcasecmp(peer->name, v->value)) { ++ ast_log(LOG_WARNING, "Invalid register '%s', same name as peer at line %d of %s\n", v->value, v->lineno, config); ++ } else { ++ add_peer_alias(peer, v->value, &lineindex); ++ } ++ } else if (!strcasecmp(v->name, "subscribe")) { ++ add_peer_subscription(peer, v->value); ++ } else if (!strcasecmp(v->name, "cisco_pickupnotify_alert")) { ++ char *val = ast_strdupa(v->value); ++ char *alert; ++ ++ ast_clear_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM | SIP_PAGE3_CISCO_PICKUPNOTIFY_TO | SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP); ++ ++ while ((alert = strsep(&val, ","))) { ++ if (!strcasecmp(alert, "none")) { ++ ast_clear_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM | SIP_PAGE3_CISCO_PICKUPNOTIFY_TO | SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP); ++ } else if (!strcasecmp(alert, "from")) { ++ ast_set_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM); ++ } else if (!strcasecmp(alert, "to")) { ++ ast_set_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_TO); ++ } else if (!strcasecmp(alert, "beep")) { ++ ast_set_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP); ++ } else { ++ ast_log(LOG_WARNING, "Invalid cisco_pickupnotify_alert '%s' at line %d of %s\n", alert, v->lineno, config); ++ } ++ } ++ } else if (!strcasecmp(v->name, "cisco_pickupnotify_timer")) { ++ if (sscanf(v->value, "%030d", &peer->cisco_pickupnotify_timer) != 1 || peer->cisco_pickupnotify_timer < 0 || peer->cisco_pickupnotify_timer > 60) { ++ ast_log(LOG_WARNING, "Invalid cisco_pickupnotify_timer '%s' at line %d of %s\n", v->value, v->lineno, config); ++ peer->cisco_pickupnotify_timer = 5; ++ } ++ } else if (!strcasecmp(v->name, "cisco_qrt_url")) { ++ ast_string_field_set(peer, cisco_qrt_url, v->value); ++ } else if (realtime && !strcasecmp(v->name, "donotdisturb")) { ++ peer->donotdisturb = ast_true(v->value); ++ } else if (realtime && !strcasecmp(v->name, "callforward")) { ++ ast_string_field_set(peer, callforward, v->value); ++ } else if (realtime && !strcasecmp(v->name, "huntgroup")) { ++ peer->huntgroup = ast_true(v->value); + } else { + ast_rtp_dtls_cfg_parse(&peer->dtls_cfg, v->name, v->value); + } +@@ -32234,6 +36974,9 @@ + + if (!devstate_only) { + struct sip_mailbox *mailbox; ++ struct sip_alias *alias; ++ struct sip_subscription *subscription; ++ + AST_LIST_TRAVERSE_SAFE_BEGIN(&peer->mailboxes, mailbox, entry) { + if (mailbox->status == SIP_MAILBOX_STATUS_UNKNOWN) { + AST_LIST_REMOVE_CURRENT(entry); +@@ -32241,6 +36984,20 @@ + } + } + AST_LIST_TRAVERSE_SAFE_END; ++ AST_LIST_TRAVERSE_SAFE_BEGIN(&peer->aliases, alias, entry) { ++ if (alias->removed) { ++ AST_LIST_REMOVE_CURRENT(entry); ++ destroy_alias(alias); ++ } ++ } ++ AST_LIST_TRAVERSE_SAFE_END; ++ AST_LIST_TRAVERSE_SAFE_BEGIN(&peer->subscriptions, subscription, entry) { ++ if (subscription->removed) { ++ AST_LIST_REMOVE_CURRENT(entry); ++ destroy_subscription(subscription); ++ } ++ } ++ AST_LIST_TRAVERSE_SAFE_END; + } + + if (!can_parse_xml && (ast_get_cc_agent_policy(peer->cc_params) == AST_CC_AGENT_NATIVE)) { +@@ -32398,6 +37155,22 @@ + reg_source_db(peer); + } + ++ if (!realtime) { ++ char data[128]; ++ ++ if (!ast_db_get("SIP/DoNotDisturb", peer->name, data, sizeof(data))) { ++ peer->donotdisturb = ast_true(data); ++ } ++ if (!ast_db_get("SIP/CallForward", peer->name, data, sizeof(data))) { ++ ast_string_field_set(peer, callforward, data); ++ } ++ if (!ast_db_get("SIP/HuntGroup", peer->name, data, sizeof(data))) { ++ peer->huntgroup = ast_true(data); ++ } else { ++ peer->huntgroup = ast_test_flag(&peer->flags[2], SIP_PAGE3_HUNTGROUP_DEFAULT) ? 1 : 0; ++ } ++ } ++ + /* If they didn't request that MWI is sent *only* on subscribe, go ahead and + * subscribe to it now. */ + if (!devstate_only && !ast_test_flag(&peer->flags[1], SIP_PAGE2_SUBSCRIBEMWIONLY) && +@@ -32406,7 +37179,12 @@ + /* Send MWI from the event cache only. This is so we can send initial + * MWI if app_voicemail got loaded before chan_sip. If it is the other + * way, then we will get events when app_voicemail gets loaded. */ +- sip_send_mwi_to_peer(peer, 1); ++ sip_send_mwi(peer, 1); ++ } ++ ++ if (!devstate_only && ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && ++ !AST_LIST_EMPTY(&peer->subscriptions)) { ++ extensionstate_subscriptions(peer); + } + + peer->the_mark = 0; +@@ -32414,9 +37192,9 @@ + oldacl = ast_free_acl_list(oldacl); + oldcontactacl = ast_free_acl_list(oldcontactacl); + olddirectmediaacl = ast_free_acl_list(olddirectmediaacl); +- if (!ast_strlen_zero(peer->callback)) { /* build string from peer info */ ++ if (!ast_strlen_zero(peer->callbackexten)) { /* build string from peer info */ + char *reg_string; +- if (ast_asprintf(®_string, "%s?%s:%s:%s@%s/%s", peer->name, S_OR(peer->fromuser, peer->username), S_OR(peer->remotesecret, peer->secret), peer->username, peer->tohost, peer->callback) >= 0) { ++ if (ast_asprintf(®_string, "%s?%s:%s:%s@%s/%s", peer->name, S_OR(peer->fromuser, peer->username), S_OR(peer->remotesecret, peer->secret), peer->username, peer->tohost, peer->callbackexten) >= 0) { + sip_register(reg_string, 0); /* XXX TODO: count in registry_count */ + ast_free(reg_string); + } +@@ -34034,6 +38812,7 @@ + static char *app_dtmfmode = "SIPDtmfMode"; + static char *app_sipaddheader = "SIPAddHeader"; + static char *app_sipremoveheader = "SIPRemoveHeader"; ++static char *app_ciscopage = "SIPCiscoPage"; + #ifdef TEST_FRAMEWORK + static char *app_sipsendcustominfo = "SIPSendCustomINFO"; + #endif +@@ -34165,6 +38944,430 @@ + return 0; + } + ++/*! \brief options for SIPCiscoPage() */ ++enum { ++ APP_CISCOPAGE_MULTICAST = 1 << 0, ++ APP_CISCOPAGE_PORT = 1 << 1, ++ APP_CISCOPAGE_VOLUME = 1 << 2, ++ APP_CISCOPAGE_DISPLAY = 1 << 3, ++ APP_CISCOPAGE_INCLUDEBUSY = 1 << 4, ++ APP_CISCOPAGE_OFFHOOK = 1 << 5, ++ APP_CISCOPAGE_BEEP = 1 << 6 ++}; ++ ++enum { ++ APP_CISCOPAGE_ARG_MULTICAST, ++ APP_CISCOPAGE_ARG_PORT, ++ APP_CISCOPAGE_ARG_VOLUME, ++ APP_CISCOPAGE_ARG_DISPLAY, ++ APP_CISCOPAGE_ARG_ARRAY_SIZE ++}; ++ ++AST_APP_OPTIONS(app_ciscopage_opts, BEGIN_OPTIONS ++ AST_APP_OPTION_ARG('m', APP_CISCOPAGE_MULTICAST, APP_CISCOPAGE_ARG_MULTICAST), ++ AST_APP_OPTION_ARG('p', APP_CISCOPAGE_PORT, APP_CISCOPAGE_ARG_PORT), ++ AST_APP_OPTION_ARG('v', APP_CISCOPAGE_VOLUME, APP_CISCOPAGE_ARG_VOLUME), ++ AST_APP_OPTION_ARG('d', APP_CISCOPAGE_DISPLAY, APP_CISCOPAGE_ARG_DISPLAY), ++ AST_APP_OPTION('b', APP_CISCOPAGE_INCLUDEBUSY), ++ AST_APP_OPTION('o', APP_CISCOPAGE_OFFHOOK), ++ AST_APP_OPTION('a', APP_CISCOPAGE_BEEP) ++END_OPTIONS); ++ ++struct page_target { ++ struct sip_peer *peer; ++ struct ast_rtp_instance *rtp; ++ AST_LIST_ENTRY(page_target) entry; ++}; ++ ++static int sip_ciscopage(struct ast_channel *chan, const char *data) ++{ ++ AST_DECLARE_APP_ARGS(args, ++ AST_APP_ARG(peernames); ++ AST_APP_ARG(options); ++ ); ++ struct ast_flags options = { 0, }; ++ char *option_args[APP_CISCOPAGE_ARG_ARRAY_SIZE]; ++ char *parse, *peername, *codec, display[64]; ++ int volume, port, multicast, includebusy, offhook, beep; ++ struct ast_format *format; ++ AST_LIST_HEAD_NOLOCK(, page_target) targets; ++ struct page_target *target; ++ struct ast_str *content = NULL; ++ struct ast_rtp_instance *mrtp = NULL; ++ struct ast_sockaddr ouraddr; ++ int res = -1; ++ ++ if (ast_strlen_zero(data)) { ++ ast_log(LOG_ERROR, "Cannot call %s without arguments\n", app_ciscopage); ++ return -1; ++ } ++ ++ parse = ast_strdupa(data); ++ AST_STANDARD_APP_ARGS(args, parse); ++ ++ if (ast_strlen_zero(args.peernames)) { ++ ast_log(LOG_ERROR, "No peer names specified\n"); ++ return -1; ++ } ++ ++ if (!ast_strlen_zero(args.options)) { ++ ast_app_parse_options(app_ciscopage_opts, &options, option_args, args.options); ++ } ++ ++ if (ast_test_flag(&options, APP_CISCOPAGE_MULTICAST) && !ast_strlen_zero(option_args[APP_CISCOPAGE_ARG_MULTICAST])) { ++ if (!ast_sockaddr_parse(&ouraddr, option_args[APP_CISCOPAGE_ARG_MULTICAST], PARSE_PORT_FORBID)) { ++ ast_log(LOG_ERROR, "Invalid IP address '%s'\n", option_args[APP_CISCOPAGE_ARG_MULTICAST]); ++ return -1; ++ } ++ ++ if (!ast_sockaddr_is_ipv4_multicast(&ouraddr)) { ++ ast_log(LOG_ERROR, "IP address '%s' is not multicast\n", option_args[APP_CISCOPAGE_ARG_MULTICAST]); ++ return -1; ++ } ++ ++ multicast = 1; ++ } else { ++ multicast = 0; ++ } ++ ++ if (ast_test_flag(&options, APP_CISCOPAGE_PORT) && !ast_strlen_zero(option_args[APP_CISCOPAGE_ARG_PORT])) { ++ port = strtol(option_args[APP_CISCOPAGE_ARG_PORT], NULL, 10); ++ if (port < 20480 || port > 32768 || port % 2) { ++ ast_log(LOG_ERROR, "Invalid port option '%s'\n", option_args[APP_CISCOPAGE_ARG_PORT]); ++ return -1; ++ } ++ } else { ++ port = 20480; ++ } ++ ++ if (ast_test_flag(&options, APP_CISCOPAGE_VOLUME) && !ast_strlen_zero(option_args[APP_CISCOPAGE_ARG_VOLUME])) { ++ volume = strtol(option_args[APP_CISCOPAGE_ARG_VOLUME], NULL, 10); ++ if (volume < 1 || volume > 100) { ++ ast_log(LOG_ERROR, "Invalid volume option '%s'\n", option_args[APP_CISCOPAGE_ARG_VOLUME]); ++ return -1; ++ } ++ } else { ++ volume = -1; ++ } ++ ++ if (ast_test_flag(&options, APP_CISCOPAGE_DISPLAY) && !ast_strlen_zero(option_args[APP_CISCOPAGE_ARG_DISPLAY])) { ++ ast_xml_escape(option_args[APP_CISCOPAGE_ARG_DISPLAY], display, sizeof(display)); ++ } else { ++ display[0] = '\0'; ++ } ++ ++ beep = ast_test_flag(&options, APP_CISCOPAGE_BEEP); ++ includebusy = ast_test_flag(&options, APP_CISCOPAGE_INCLUDEBUSY); ++ offhook = ast_test_flag(&options, APP_CISCOPAGE_OFFHOOK); ++ ++ format = ast_channel_readformat(chan); ++ if (ast_format_cmp(format, ast_format_ulaw) == AST_FORMAT_CMP_EQUAL || ast_format_cmp(format, ast_format_alaw) == AST_FORMAT_CMP_EQUAL) { ++ codec = "G.711"; ++ } else if (ast_format_cmp(format, ast_format_g722) == AST_FORMAT_CMP_EQUAL) { ++ codec = "G.722"; ++ } else if (ast_format_cmp(format, ast_format_g729) == AST_FORMAT_CMP_EQUAL) { ++ codec = "G.729"; ++ } else { ++ ast_log(LOG_ERROR, "Unsupported codec format\n"); ++ return -1; ++ } ++ ++ content = ast_str_create(8192); ++ AST_LIST_HEAD_INIT_NOLOCK(&targets); ++ ++ while ((peername = strsep(&args.peernames, "&"))) { ++ struct sip_peer *peer; ++ struct sip_pvt *pvt; ++ struct ast_rtp_instance *rtp = NULL; ++ struct ast_sockaddr theiraddr; ++ ++ if (!(peer = sip_find_peer(peername, NULL, TRUE, FINDPEERS, FALSE, 0))) { ++ ast_log(LOG_ERROR, "No such peer '%s'\n", peername); ++ continue; ++ } ++ ++ if (!ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { ++ ast_log(LOG_ERROR, "Peer '%s' does not have cisco_usecallmanager enabled\n", peer->name); ++ sip_unref_peer(peer, "unref peer"); ++ continue; ++ } ++ ++ if (ast_sockaddr_isnull(&peer->addr)) { ++ ast_log(LOG_ERROR, "Peer '%s' is not registered\n", peer->name); ++ sip_unref_peer(peer, "unref peer"); ++ continue; ++ } ++ ++ if ((peer->offhook || peer->ringing || peer->inuse || peer->donotdisturb) && !includebusy) { ++ sip_unref_peer(peer, "unref peer"); ++ continue; ++ } ++ ++ if (!multicast) { ++ /* We only need the socket type for ast_sip_ouraddrfor */ ++ struct sip_pvt pvt = { .socket.type = peer->socket.type }; ++ ++ ast_sip_ouraddrfor(&peer->addr, &ouraddr, &pvt); ++ ast_sockaddr_copy(&theiraddr, &peer->addr); ++ ast_sockaddr_set_port(&theiraddr, port); ++ ++ if (!(rtp = ast_rtp_instance_new(peer->engine, sched, &ouraddr, NULL))) { ++ sip_unref_peer(peer, "unref peer"); ++ goto ciscopage_cleanup; ++ } ++ ++ ast_rtp_instance_set_write_format(rtp, ast_channel_readformat(chan)); ++ ast_rtp_instance_set_remote_address(rtp, &theiraddr); ++ ++ ast_rtp_instance_set_qos(rtp, global_tos_audio, global_cos_audio, "SIP RTP"); ++ ast_rtp_instance_activate(rtp); ++ } ++ ++ if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ ast_log(LOG_ERROR, "Unable to build sip pvt data for refer (memory/socket error)\n"); ++ sip_unref_peer(peer, "unref peer"); ++ if (rtp) { ++ ast_rtp_instance_destroy(rtp); ++ } ++ continue; ++ } ++ ++ set_socket_transport(&pvt->socket, 0); ++ if (create_addr_from_peer(pvt, peer)) { ++ dialog_unlink_all(pvt); ++ dialog_unref(pvt, "create_addr_from_peer failed. unref dialog"); ++ sip_unref_peer(peer, "unref peer"); ++ if (rtp) { ++ ast_rtp_instance_destroy(rtp); ++ } ++ continue; ++ } ++ ++ ast_str_reset(content); ++ ++ if (!ast_strlen_zero(display)) { ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "notify_display\n"); ++ ast_str_append(&content, 0, "%s\n", display); ++ ast_str_append(&content, 0, "10\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "1\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ } ++ ++ if (beep) { ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "DtZipZip\n"); ++ ast_str_append(&content, 0, "all\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ } ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "StationSequenceLast\n"); ++ ast_str_append(&content, 0, "2\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ if (volume != -1) { ++ ast_str_append(&content, 0, "\n", volume); ++ } else { ++ ast_str_append(&content, 0, "\n"); ++ } ++ ast_str_append(&content, 0, "audio\n"); ++ ast_str_append(&content, 0, "%s\n", codec); ++ ast_str_append(&content, 0, "receive\n"); ++ ast_str_append(&content, 0, "
%s
\n", ast_sockaddr_stringify_fmt(&ouraddr, AST_SOCKADDR_STR_ADDR)); ++ ast_str_append(&content, 0, "%d\n", port); ++ ast_str_append(&content, 0, "
\n"); ++ ast_str_append(&content, 0, "
\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(pvt, "drop pvt"); ++ ++ if (!(target = ast_calloc(1, sizeof(*target)))) { ++ sip_unref_peer(peer, "unref peer"); ++ if (rtp) { ++ ast_rtp_instance_destroy(rtp); ++ } ++ goto ciscopage_cleanup; ++ } ++ ++ if (offhook) { ++ ao2_lock(peer); ++ peer->offhook += 1; ++ ao2_unlock(peer); ++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); ++ } ++ ++ target->peer = peer; ++ target->rtp = rtp; ++ ++ AST_LIST_INSERT_TAIL(&targets, target, entry); ++ } ++ ++ if (AST_LIST_EMPTY(&targets)) { ++ ast_free(content); ++ return -1; ++ } ++ ++ if (multicast) { ++ ast_sockaddr_set_port(&ouraddr, port); ++ ++ if (!(mrtp = ast_rtp_instance_new("multicast", sched, &bindaddr, "basic"))) { ++ goto ciscopage_cleanup; ++ } ++ ++ ast_rtp_instance_set_write_format(mrtp, ast_channel_readformat(chan)); ++ ast_rtp_instance_set_remote_address(mrtp, &ouraddr); ++ ++ ast_rtp_instance_set_qos(mrtp, global_tos_audio, global_cos_audio, "SIP RTP"); ++ ast_rtp_instance_activate(mrtp); ++ } ++ ++ if (ast_channel_state(chan) != AST_STATE_UP) { ++ if ((res = ast_answer(chan))) { ++ goto ciscopage_cleanup; ++ } ++ } ++ ++ /* Wait 500ms for phones to accept the media stream request */ ++ if ((res = ast_safe_sleep(chan, 500))) { ++ goto ciscopage_cleanup; ++ } ++ ++ for (;;) { ++ struct ast_frame *frame; ++ ++ if (ast_waitfor(chan, 10000) < 1) { ++ break; ++ } ++ frame = ast_read(chan); ++ ++ if (!frame || (frame->frametype == AST_FRAME_CONTROL && frame->subclass.integer == AST_CONTROL_HANGUP)) { ++ if (frame) { ++ ast_frfree(frame); ++ } ++ break; ++ } ++ ++ if (frame->frametype == AST_FRAME_VOICE) { ++ if (mrtp) { ++ ast_rtp_instance_write(mrtp, frame); ++ } else { ++ AST_LIST_TRAVERSE(&targets, target, entry) { ++ ast_rtp_instance_write(target->rtp, frame); ++ } ++ } ++ } ++ ++ ast_frfree(frame); ++ } ++ ++ res = 0; ++ ++ciscopage_cleanup: ++ while ((target = AST_LIST_REMOVE_HEAD(&targets, entry))) { ++ struct sip_pvt *pvt; ++ ++ if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { ++ ast_log(LOG_ERROR, "Unable to build sip pvt data for refer (memory/socket error)\n"); ++ } else { ++ set_socket_transport(&pvt->socket, 0); ++ if (create_addr_from_peer(pvt, target->peer)) { ++ dialog_unlink_all(pvt); ++ dialog_unref(pvt, "create_addr_from_peer failed. unref dialog"); ++ pvt = NULL; ++ } ++ } ++ ++ if (pvt) { ++ ast_str_reset(content); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "StationSequenceLast\n"); ++ ast_str_append(&content, 0, "2\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "0\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ++ ast_str_append(&content, 0, "--uniqueBoundary\r\n"); ++ ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\n"); ++ ast_str_append(&content, 0, "\r\n"); ++ ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); ++ ++ transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); ++ dialog_unref(pvt, "drop pvt"); ++ } ++ ++ if (offhook) { ++ ao2_lock(target->peer); ++ target->peer->offhook -= 1; ++ ao2_unlock(target->peer); ++ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", target->peer->name); ++ } ++ ++ sip_unref_peer(target->peer, "unref peer"); ++ if (target->rtp) { ++ ast_rtp_instance_destroy(target->rtp); ++ } ++ ast_free(target); ++ } ++ ++ if (mrtp) { ++ ast_rtp_instance_destroy(mrtp); ++ } ++ ast_free(content); ++ ++ return res; ++} ++ + #ifdef TEST_FRAMEWORK + /*! \brief Send a custom INFO message via AST_CONTROL_CUSTOM indication */ + static int sip_sendcustominfo(struct ast_channel *chan, const char *data) +@@ -34270,6 +39473,11 @@ + i = ao2_iterator_init(peers, 0); + while ((peer = ao2_t_iterator_next(&i, "iterate thru peers table"))) { + ao2_lock(peer); ++ /* Only poke the primary line */ ++ if (peer->cisco_lineindex > 1) { ++ ao2_unlock(peer); ++ continue; ++ } + /* Don't schedule poking on a peer without qualify */ + if (peer->maxms) { + if (num == global_qualify_peers) { +@@ -34415,6 +39623,9 @@ + /* Send qualify (OPTIONS) to all peers */ + sip_poke_all_peers(); + ++ /* Register aliases now that all peers have been added */ ++ register_all_aliases(); ++ + /* Send keepalive to all peers */ + sip_keepalive_all_peers(); + +@@ -34561,9 +39772,9 @@ + static int peer_ipcmp_cb_full(void *obj, void *arg, void *data, int flags) + { + struct sip_peer *peer = obj, *peer2 = arg; +- char *callback = data; ++ char *callbackexten = data; + +- if (!ast_strlen_zero(callback) && strcasecmp(peer->callback, callback)) { ++ if (!ast_strlen_zero(callbackexten) && strcasecmp(peer->callbackexten, callbackexten)) { + /* We require a callback extension match, but don't have one */ + return 0; + } +@@ -34725,6 +39936,9 @@ + AST_CLI_DEFINE(sip_show_settings, "Show SIP global settings"), + AST_CLI_DEFINE(sip_show_mwi, "Show MWI subscriptions"), + AST_CLI_DEFINE(sip_cli_notify, "Send a notify packet to a SIP peer"), ++ AST_CLI_DEFINE(sip_cli_donotdisturb, "Enables/Disables do not disturb on a SIP peer"), ++ AST_CLI_DEFINE(sip_cli_callforward, "Sets/Removes the call forwarding extension for a SIP peer"), ++ AST_CLI_DEFINE(sip_cli_huntgroup, "Login to/Logout from Hunt Group for a SIP peer"), + AST_CLI_DEFINE(sip_show_channel, "Show detailed SIP channel info"), + AST_CLI_DEFINE(sip_show_history, "Show SIP dialog history"), + AST_CLI_DEFINE(sip_show_peer, "Show details on specific SIP peer"), +@@ -35634,6 +40848,7 @@ + ast_register_application_xml(app_dtmfmode, sip_dtmfmode); + ast_register_application_xml(app_sipaddheader, sip_addheader); + ast_register_application_xml(app_sipremoveheader, sip_removeheader); ++ ast_register_application_xml(app_ciscopage, sip_ciscopage); + #ifdef TEST_FRAMEWORK + ast_register_application_xml(app_sipsendcustominfo, sip_sendcustominfo); + #endif +@@ -35708,6 +40923,7 @@ + + sip_register_tests(); + network_change_stasis_subscribe(); ++ pickup_notify_stasis_subscribe(); + + if (sip_cfg.websocket_enabled) { + ast_websocket_add_protocol("sip", sip_websocket_callback); +@@ -35740,6 +40956,7 @@ + + network_change_stasis_unsubscribe(); + acl_change_event_stasis_unsubscribe(); ++ pickup_notify_stasis_unsubscribe(); + + /* First, take us out of the channel type list */ + ast_channel_unregister(&sip_tech); +diff -durN asterisk-20.7.0.orig/channels/sip/config_parser.c asterisk-20.7.0/channels/sip/config_parser.c +--- asterisk-20.7.0.orig/channels/sip/config_parser.c 2024-04-04 00:48:07.188695806 +1300 ++++ asterisk-20.7.0/channels/sip/config_parser.c 2024-04-04 00:48:46.663615233 +1300 +@@ -255,7 +255,7 @@ + } + + /* copy into sip_registry object */ +- ast_string_field_set(reg, callback, ast_strip_quoted(S_OR(host2.extension, "s"), "\"", "\"")); ++ ast_string_field_set(reg, exten, ast_strip_quoted(S_OR(host2.extension, "s"), "\"", "\"")); + ast_string_field_set(reg, username, ast_strip_quoted(S_OR(user2.user, ""), "\"", "\"")); + ast_string_field_set(reg, hostname, ast_strip_quoted(S_OR(host3.host, ""), "\"", "\"")); + ast_string_field_set(reg, authuser, ast_strip_quoted(S_OR(user3.authuser, ""), "\"", "\"")); +@@ -310,7 +310,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg1, 1) || +- strcmp(reg->callback, "s") || ++ strcmp(reg->exten, "s") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "") || + strcmp(reg->hostname, "domain") || +@@ -339,7 +339,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg2, 1) || +- strcmp(reg->callback, "s") || ++ strcmp(reg->exten, "s") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "") || + strcmp(reg->hostname, "domain") || +@@ -368,7 +368,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg3, 1) || +- strcmp(reg->callback, "s") || ++ strcmp(reg->exten, "s") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "namedomain") || + strcmp(reg->hostname, "domain") || +@@ -397,7 +397,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg4, 1) || +- strcmp(reg->callback, "extension") || ++ strcmp(reg->exten, "extension") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "namedomain") || + strcmp(reg->hostname, "domain") || +@@ -426,7 +426,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg5, 1) || +- strcmp(reg->callback, "extension") || ++ strcmp(reg->exten, "extension") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "namedomain") || + strcmp(reg->hostname, "domain") || +@@ -455,7 +455,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg6, 1) || +- strcmp(reg->callback, "extension") || ++ strcmp(reg->exten, "extension") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "namedomain") || + strcmp(reg->hostname, "domain") || +@@ -484,7 +484,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg7, 1) || +- strcmp(reg->callback, "extension") || ++ strcmp(reg->exten, "extension") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "namedomain") || + strcmp(reg->hostname, "domain") || +@@ -513,7 +513,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg8, 1) || +- strcmp(reg->callback, "extension") || ++ strcmp(reg->exten, "extension") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "namedomain") || + strcmp(reg->hostname, "domain") || +@@ -583,7 +583,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg12, 1) || +- strcmp(reg->callback, "s") || ++ strcmp(reg->exten, "s") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "namedomain") || + strcmp(reg->hostname, "domain") || +@@ -612,7 +612,7 @@ + goto alloc_fail; + } else if ( + sip_parse_register_line(reg, default_expiry, reg13, 1) || +- strcmp(reg->callback, "s") || ++ strcmp(reg->exten, "s") || + strcmp(reg->username, "name") || + strcmp(reg->regdomain, "namedomain") || + strcmp(reg->hostname, "domain") || +diff -durN asterisk-20.7.0.orig/channels/sip/include/sip.h asterisk-20.7.0/channels/sip/include/sip.h +--- asterisk-20.7.0.orig/channels/sip/include/sip.h 2024-04-04 00:48:07.188695806 +1300 ++++ asterisk-20.7.0/channels/sip/include/sip.h 2024-04-04 00:48:46.667615123 +1300 +@@ -37,6 +37,7 @@ + #include "asterisk/rtp_engine.h" + #include "asterisk/netsock2.h" + #include "asterisk/features_config.h" ++#include "asterisk/stasis.h" + + #include "route.h" + +@@ -107,7 +108,7 @@ + #define SIP_MIN_PACKET 4096 /*!< Initialize size of memory to allocate for packets */ + #define MAX_HISTORY_ENTRIES 50 /*!< Max entires in the history list for a sip_pvt */ + +-#define INITIAL_CSEQ 101 /*!< Our initial sip sequence number */ ++#define INITIAL_CSEQ 100 /*!< Our initial sip sequence number */ + + #define DEFAULT_MAX_SE 1800 /*!< Session-Timer Default Session-Expires period (RFC 4028) */ + #define DEFAULT_MIN_SE 90 /*!< Session-Timer Default Min-SE period (RFC 4028) */ +@@ -351,7 +352,7 @@ + #define SIP_PAGE2_CALL_ONHOLD_INACTIVE (3 << 19) /*!< D: Inactive hold */ + + #define SIP_PAGE2_RFC2833_COMPENSATE (1 << 21) /*!< DP: Compensate for buggy RFC2833 implementations */ +-#define SIP_PAGE2_BUGGY_MWI (1 << 22) /*!< DP: Buggy CISCO MWI fix */ ++#define SIP_PAGE2_CISCO_USECALLMANAGER (1 << 22) /*!< DP: Enable Cisco USECALLMANAGER phone support */ + #define SIP_PAGE2_DIALOG_ESTABLISHED (1 << 23) /*!< 29: Has a dialog been established? */ + + #define SIP_PAGE2_FAX_DETECT (3 << 24) /*!< DP: Fax Detection support */ +@@ -372,7 +373,7 @@ + #define SIP_PAGE2_FLAGS_TO_COPY \ + (SIP_PAGE2_ALLOWSUBSCRIBE | SIP_PAGE2_ALLOWOVERLAP | SIP_PAGE2_IGNORESDPVERSION | \ + SIP_PAGE2_VIDEOSUPPORT | SIP_PAGE2_T38SUPPORT | SIP_PAGE2_RFC2833_COMPENSATE | \ +- SIP_PAGE2_BUGGY_MWI | SIP_PAGE2_TEXTSUPPORT | SIP_PAGE2_FAX_DETECT | \ ++ SIP_PAGE2_CISCO_USECALLMANAGER | SIP_PAGE2_TEXTSUPPORT | SIP_PAGE2_FAX_DETECT | \ + SIP_PAGE2_UDPTL_DESTINATION | SIP_PAGE2_VIDEOSUPPORT_ALWAYS | SIP_PAGE2_PREFERRED_CODEC | \ + SIP_PAGE2_RPID_IMMEDIATE | SIP_PAGE2_RPID_UPDATE | SIP_PAGE2_SYMMETRICRTP |\ + SIP_PAGE2_Q850_REASON | SIP_PAGE2_HAVEPEERCONTEXT | SIP_PAGE2_USE_SRTP | SIP_PAGE2_TRUST_ID_OUTBOUND) +@@ -389,11 +390,29 @@ + #define SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL (1 << 8) /*!< DGP: Stop telling the peer to start music on hold */ + #define SIP_PAGE3_FORCE_AVP (1 << 9) /*!< DGP: Force 'RTP/AVP' for all streams, even DTLS */ + #define SIP_PAGE3_RTCP_MUX (1 << 10) /*!< DGP: Attempt to negotiate RFC 5761 RTCP multiplexing */ ++#define SIP_PAGE3_WHY_DIDNT_DIGIUM_THINK_OF_THIS (1 << 11) /*!< Duh */ ++#define SIP_PAGE3_DND_BUSY (1 << 12) /*!< DPG: Treat endpoint as busy when DND is enabled */ ++#define SIP_PAGE3_SUBSCRIPTIONSTATE_ACTIVE (1 << 13) /*!< D: Force Subscription-State to be active for NOTIFYs */ ++#define SIP_PAGE3_CISCO_KEEP_CONFERENCE (1 << 14) /*!< DGP: Keep ad-hoc conference after initiator hangs up */ ++#define SIP_PAGE3_CISCO_MULTIADMIN_CONFERENCE (1 << 15) /*!<< DGP: Allow participants to administrate conference */ ++#define SIP_PAGE3_RELAY_NEAREND (1 << 16) /*!< D: Add x-relay-nearend attribute to SDP */ ++#define SIP_PAGE3_RELAY_FAREND (1 << 17) /*!< D: Add x-relay-farend attribute to SDP */ ++#define SIP_PAGE3_SDP_ACK (1 << 18) /*!< D: Add SDP to ACK */ ++#define SIP_PAGE3_CISCO_RECORDING (1 << 19) /*!< D: Call is now being recorded */ ++#define SIP_PAGE3_RTP_STATS_ON_BYE (1 << 20) /*!< D: Display RTP stats on BYE */ ++#define SIP_PAGE3_HUNTGROUP_DEFAULT (1 << 21) /*!< GP: If peer is logged into huntgroup by default */ ++#define SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM (1 << 22) /*!> P: Include the from number in the statusline for pickup notify */ ++#define SIP_PAGE3_CISCO_PICKUPNOTIFY_TO (1 << 23) /*!> P: Include the to number in the statusline for pickup notify */ ++#define SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP (1 << 24) /*!> P: Play a beep for pickup notify */ ++#define SIP_PAGE3_TRANSFER_RESPONSE (1 << 25) /*!< D: Send specific transfer-response for call */ + + #define SIP_PAGE3_FLAGS_TO_COPY \ + (SIP_PAGE3_SNOM_AOC | SIP_PAGE3_SRTP_TAG_32 | SIP_PAGE3_NAT_AUTO_RPORT | SIP_PAGE3_NAT_AUTO_COMEDIA | \ + SIP_PAGE3_DIRECT_MEDIA_OUTGOING | SIP_PAGE3_USE_AVPF | SIP_PAGE3_ICE_SUPPORT | SIP_PAGE3_IGNORE_PREFCAPS | \ +- SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL | SIP_PAGE3_FORCE_AVP | SIP_PAGE3_RTCP_MUX) ++ SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL | SIP_PAGE3_FORCE_AVP | SIP_PAGE3_RTCP_MUX | \ ++ SIP_PAGE3_WHY_DIDNT_DIGIUM_THINK_OF_THIS | SIP_PAGE3_DND_BUSY | SIP_PAGE3_CISCO_KEEP_CONFERENCE | \ ++ SIP_PAGE3_CISCO_MULTIADMIN_CONFERENCE | SIP_PAGE3_HUNTGROUP_DEFAULT | SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM | \ ++ SIP_PAGE3_CISCO_PICKUPNOTIFY_TO | SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP) + + #define CHECK_AUTH_BUF_INITLEN 256 + +@@ -475,6 +494,9 @@ + PIDF_XML, + MWI_NOTIFICATION, + CALL_COMPLETION, ++ PRESENCE, ++ FEATURE_EVENTS, ++ REMOTECC_XML, + }; + + /*! \brief The number of media types in enum \ref media_type below. */ +@@ -940,10 +962,14 @@ + AST_STRING_FIELD(replaces_callid); /*!< Replace info: callid */ + AST_STRING_FIELD(replaces_callid_totag); /*!< Replace info: to-tag */ + AST_STRING_FIELD(replaces_callid_fromtag); /*!< Replace info: from-tag */ ++ AST_STRING_FIELD(require); /*!< Outgoing Require header */ ++ AST_STRING_FIELD(content_id); /*!< Outgoing Content-ID header */ ++ AST_STRING_FIELD(content_type); /*!< Outgoing Content-Type header */ + ); + int attendedtransfer; /*!< Attended or blind transfer? */ + int localtransfer; /*!< Transfer to local domain? */ + enum referstatus status; /*!< REFER status */ ++ struct ast_str *content; /*!< Outgoing content body */ + }; + + /*! \brief Struct to handle custom SIP notify requests. Dynamically allocated when needed */ +@@ -1059,6 +1085,10 @@ + AST_STRING_FIELD(msg_body); /*!< Text for a MESSAGE body */ + AST_STRING_FIELD(tel_phone_context); /*!< The phone-context portion of a TEL URI */ + AST_STRING_FIELD(sessionunique_remote); /*!< Remote UA's SDP Session unique parts */ ++ AST_STRING_FIELD(callforward); /*!< Call Forward target */ ++ AST_STRING_FIELD(join_callid); /*!< Join callid */ ++ AST_STRING_FIELD(join_tag); /*!< Join tag */ ++ AST_STRING_FIELD(join_theirtag); /*!< Join theirtag */ + ); + char via[128]; /*!< Via: header */ + int maxforwards; /*!< SIP Loop prevention */ +@@ -1184,6 +1214,9 @@ + struct ast_sdp_srtp *srtp; /*!< Structure to hold Secure RTP session data for audio */ + struct ast_sdp_srtp *vsrtp; /*!< Structure to hold Secure RTP session data for video */ + struct ast_sdp_srtp *tsrtp; /*!< Structure to hold Secure RTP session data for text */ ++ int donotdisturb:1; /*!< Peer has set DoNotDisturb */ ++ struct sip_pvt *recordoutpvt; /*!< Pvt for the outbound recording leg */ ++ struct sip_pvt *recordinpvt; /*!< Pvt for the inbound recording leg */ + + int red; /*!< T.140 RTP Redundancy */ + int hangupcause; /*!< Storage of hangupcause copied from our owner before we disconnect from the AST channel (only used at hangup) */ +@@ -1217,6 +1250,7 @@ + struct ast_cc_config_params *cc_params; + struct sip_epa_entry *epa_entry; + int fromdomainport; /*!< Domain port to show in from field */ ++ struct sip_conference *conference; /*!< Ad-hoc n-way conference support */ + + struct ast_rtp_dtls_cfg dtls_cfg; + }; +@@ -1266,6 +1300,30 @@ + char id[1]; + }; + ++/*! ++ * \brief A peer's bulk register aliases ++ */ ++struct sip_alias { ++ char *name; ++ AST_LIST_ENTRY(sip_alias) entry; ++ struct sip_peer *peer; ++ int lineindex; ++ unsigned int removed:1; ++}; ++ ++/*! ++ * \brief A peer's subscription ++ */ ++struct sip_subscription { ++ struct sip_pvt *pvt; ++ AST_LIST_ENTRY(sip_subscription) entry; ++ unsigned int removed:1; ++ AST_DECLARE_STRING_FIELDS( ++ AST_STRING_FIELD(exten); ++ AST_STRING_FIELD(context); ++ ); ++}; ++ + /*! \brief Structure for SIP peer data, we place calls to peers if registered or fixed IP address (host) + */ + /* XXX field 'name' must be first otherwise sip_addrcmp() will fail, as will astobj2 hashing of the structure */ +@@ -1301,7 +1359,15 @@ + AST_STRING_FIELD(zone); /*!< Tonezone for this device */ + AST_STRING_FIELD(record_on_feature); /*!< Feature to use when receiving INFO with record: on during a call */ + AST_STRING_FIELD(record_off_feature); /*!< Feature to use when receiving INFO with record: off during a call */ +- AST_STRING_FIELD(callback); /*!< Callback extension */ ++ AST_STRING_FIELD(callbackexten); /*!< Callback extension */ ++ AST_STRING_FIELD(callforward); /*!< Call forwarding extension */ ++ AST_STRING_FIELD(regcallid); /*!< Call-ID of the REGISTER dialog */ ++ AST_STRING_FIELD(cisco_authname); /*!< Name of the primary line */ ++ AST_STRING_FIELD(cisco_softkey); /*!< Name of the last soft-key sent via remotecc */ ++ AST_STRING_FIELD(cisco_devicename); /*!< Name of the device */ ++ AST_STRING_FIELD(cisco_activeload); /*!< Name of the active firmware load */ ++ AST_STRING_FIELD(cisco_inactiveload); /*!< Name of the inactive firmware load */ ++ AST_STRING_FIELD(cisco_qrt_url); /*< QRT URL */ + ); + struct sip_socket socket; /*!< Socket used for this peer */ + enum ast_transport default_outbound_transport; /*!< Peer Registration may change the default outbound transport. +@@ -1324,6 +1390,9 @@ + int inuse; /*!< Number of calls in use */ + int ringing; /*!< Number of calls ringing */ + int onhold; /*!< Peer has someone on hold */ ++ int donotdisturb:1; /*!< Peer has set DoNotDisturb */ ++ int huntgroup:1; /*!< Peer is logged into the HuntGroup */ ++ int offhook; /*!< Peer has signalled that they are off-hook */ + int call_limit; /*!< Limit of concurrent calls */ + unsigned int t38_maxdatagram; /*!< T.38 FaxMaxDatagram override */ + int busy_level; /*!< Level of active channels where we signal busy */ +@@ -1336,6 +1405,15 @@ + /*! Mailboxes that this peer cares about */ + AST_LIST_HEAD_NOLOCK(, sip_mailbox) mailboxes; + ++ /*! Bulk register aliases that this peer cares about */ ++ AST_LIST_HEAD_NOLOCK(, sip_alias) aliases; ++ ++ /*! Subscriptions that this peer cares about */ ++ AST_LIST_HEAD_NOLOCK(, sip_subscription) subscriptions; ++ ++ /*! Dialogs selected for joining */ ++ AST_LIST_HEAD_NOLOCK(, sip_selected) selected; ++ + int maxcallbitrate; /*!< Maximum Bitrate for a video call */ + int expire; /*!< When to expire this peer registration */ + struct ast_format_cap *caps; /*!< Codec capability */ +@@ -1364,11 +1442,16 @@ + struct ast_acl_list *directmediaacl; /*!< Restrict what IPs are allowed to interchange direct media with */ + struct ast_variable *chanvars; /*!< Variables to set for channel created by user */ + struct sip_pvt *mwipvt; /*!< Subscription for MWI */ ++ struct sip_pvt *fepvt; /*!< Subscription for Feature Events */ ++ struct sip_callback *callback; /*!< Extension State watcher for Call Back requests */ + struct sip_st_cfg stimer; /*!< SIP Session-Timers */ + int timer_t1; /*!< The maximum T1 value for the peer */ + int timer_b; /*!< The maximum timer B (transaction timeouts) */ + int fromdomainport; /*!< The From: domain port */ + struct sip_route path; /*!< List of out-of-dialog outgoing routing steps (fm Path headers) */ ++ int cisco_lineindex; /*!< Line index number */ ++ int cisco_pickupnotify_timer; /*!< Toast timer for pickup notify */ ++ time_t cisco_pickupnotify_sent; /*!< Last time a pickup notify was sent */ + + /*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 */ +@@ -1407,7 +1490,7 @@ + AST_STRING_FIELD(hostname); /*!< Domain or host we register to */ + AST_STRING_FIELD(secret); /*!< Password in clear text */ + AST_STRING_FIELD(md5secret); /*!< Password in md5 */ +- AST_STRING_FIELD(callback); /*!< Contact extension */ ++ AST_STRING_FIELD(exten); /*!< Contact extension */ + AST_STRING_FIELD(peername); /*!< Peer registering to */ + AST_STRING_FIELD(localtag); /*!< Local tag generated same time as callid */ + ); +@@ -1448,6 +1531,50 @@ + }; + + /*! ++ * \brief Container for server-side n-way conference bridging ++ */ ++struct sip_conference { ++ int confid; ++ int next_callid; ++ struct ast_bridge *bridge; ++ int keep:1; ++ int multiadmin:1; ++ int administrators; ++ int users; ++ AST_LIST_HEAD_NOLOCK(, sip_participant) participants; ++ AST_LIST_ENTRY(sip_conference) entry; ++}; ++ ++struct sip_participant { ++ int callid; ++ struct sip_conference *conference; ++ struct ast_channel *chan; ++ int administrator:1; ++ int removed:1; ++ int muted:1; ++ int talking:1; ++ AST_LIST_ENTRY(sip_participant) entry; ++}; ++ ++struct sip_selected { ++ AST_DECLARE_STRING_FIELDS( ++ AST_STRING_FIELD(callid); ++ AST_STRING_FIELD(tag); ++ AST_STRING_FIELD(theirtag); ++ ); ++ AST_LIST_ENTRY(sip_selected) entry; ++}; ++ ++/*! ++ * \brief Container for peer callback (camp-on) watcher ++ */ ++struct sip_callback { ++ int stateid; ++ char *exten; ++ int busy:1; ++}; ++ ++/*! + * \brief Definition of an MWI subscription to another server + */ + struct sip_subscription_mwi { +diff -durN asterisk-20.7.0.orig/configs/samples/res_parking.conf.sample asterisk-20.7.0/configs/samples/res_parking.conf.sample +--- asterisk-20.7.0.orig/configs/samples/res_parking.conf.sample 2024-04-04 00:48:07.192695696 +1300 ++++ asterisk-20.7.0/configs/samples/res_parking.conf.sample 2024-04-04 00:48:46.667615123 +1300 +@@ -68,6 +68,7 @@ + ; extensions if parkext is set + + ;parkingtime => 45 ; Number of seconds a call can be parked before returning ++;remindertime => 0 ; Number of seconds before sending a reminder, 0 for no reminder. + + ;comebacktoorigin = yes ; Setting this option configures the behavior of call parking when the + ; parked call times out (See the parkingtime option). The default value is 'yes'. +diff -durN asterisk-20.7.0.orig/configs/samples/sip.conf.sample asterisk-20.7.0/configs/samples/sip.conf.sample +--- asterisk-20.7.0.orig/configs/samples/sip.conf.sample 2024-04-04 00:48:07.192695696 +1300 ++++ asterisk-20.7.0/configs/samples/sip.conf.sample 2024-04-04 00:48:46.671615014 +1300 +@@ -1602,6 +1602,22 @@ + ;defaultip=192.168.0.4 ; IP address to use until registration + ;defaultuser=goran ; Username to use when calling this device before registration + ; Normally you do NOT need to set this parameter ++;dndbusy=yes ; Automatically send back a busy signal when trying to call a peer that is DND ++;huntgroup_default=yes ; If peer is logged into huntgroup by default ++;cisco_usecallmanager=yes ; Enable Cisco phone features, required to make hints, presence and call-forwarding work, ++ ; DO NOT enable this on any other type of device ++;subscribe=123 ; cisco_usecallmanager phones don't SUBSCRIBE to hints so they need to be configured ++ ; both in SEPMAC.cnf.xml and here as well ++;register=cisco2 ; Cisco phones on register their first line, so any additional lines must be ++ ; registered by specifying the name of the peer. ++;cisco_keep_conference=no ; When there are no more administrators in an ad-hoc conference hang up the other ++ ; participants ++;cisco_multiadmin_conference=yes ; If the participant added to an ad-hoc conference has cisco_usecallmanager=yes and ++ ; cisco_multiadmin_conference=yes then make them an administrator as well ++;cisco_pickupnotify_alert=from,to,beep ; Send an alert to the phone if another phone in the pickup group is ringing. ++ ; 'from' - show the caller number, 'to' - show the callee number, ++ ; 'beep' - play a soft beep tone, 'none' - disabled. ++;cisco_pickupnotify_timer=5 ; Display timeout for pickup notify when either 'from' or 'to' are used. + ;setvar=CUSTID=5678 ; Channel variable to be set for all calls from or to this device + ;setvar=ATTENDED_TRANSFER_COMPLETE_SOUND=beep ; This channel variable will + ; cause the given audio file to +diff -durN asterisk-20.7.0.orig/configs/samples/sip_notify.conf.sample asterisk-20.7.0/configs/samples/sip_notify.conf.sample +--- asterisk-20.7.0.orig/configs/samples/sip_notify.conf.sample 2024-04-04 00:48:07.192695696 +1300 ++++ asterisk-20.7.0/configs/samples/sip_notify.conf.sample 2024-04-04 00:48:46.671615014 +1300 +@@ -55,3 +55,34 @@ + + [cisco-check-cfg] + Event=>check-sync ++ ++[cisco-restart] ++Event=>service-control ++Subscription-State=>active ++Content-Type=>text/plain ++Content=>action=restart ++Content=>RegisterCallId={${SIPPEER(${PEERNAME},regcallid)}} ++Content=>ConfigVersionStamp={00000000-0000-0000-0000-000000000000} ++Content=>DialplanVersionStamp={00000000-0000-0000-0000-000000000000} ++Content=>SoftkeyVersionStamp={00000000-0000-0000-0000-000000000000} ++Content=>FeatureControlVersionStamp={00000000-0000-0000-0000-000000000000} ++Content=>HeadsetVersionStamp={0-0000000000} ++ ++[cisco-reset] ++Event=>service-control ++Subscription-State=>active ++Content-Type=>text/plain ++Content=>action=reset ++Content=>RegisterCallId={${SIPPEER(${PEERNAME},regcallid)}} ++Content=>ConfigVersionStamp={00000000-0000-0000-0000-000000000000} ++Content=>DialplanVersionStamp={00000000-0000-0000-0000-000000000000} ++Content=>SoftkeyVersionStamp={00000000-0000-0000-0000-000000000000} ++Content=>FeatureControlVersionStamp={00000000-0000-0000-0000-000000000000} ++Content=>HeadsetVersionStamp={0-0000000000} ++ ++[cisco-prt-report] ++Event=>service-control ++Subscription-State=>active ++Content-Type=>text/plain ++Content=>action=prt-report ++Content=>RegisterCallId={${SIPPEER(${PEERNAME},regcallid)}} +diff -durN asterisk-20.7.0.orig/contrib/realtime/mysql/mysql_config.sql asterisk-20.7.0/contrib/realtime/mysql/mysql_config.sql +--- asterisk-20.7.0.orig/contrib/realtime/mysql/mysql_config.sql 2024-04-04 00:48:07.196695587 +1300 ++++ asterisk-20.7.0/contrib/realtime/mysql/mysql_config.sql 2024-04-04 00:48:46.675614904 +1300 +@@ -95,6 +95,9 @@ + dynamic ENUM('yes','no'), + path VARCHAR(256), + supportpath ENUM('yes','no'), ++ donotdisturb ENUM('yes','no'), ++ callforward VARCHAR(40), ++ huntgroup ENUM('yes','no'), + PRIMARY KEY (id), + UNIQUE (name) + ); +diff -durN asterisk-20.7.0.orig/contrib/realtime/postgresql/postgresql_config.sql asterisk-20.7.0/contrib/realtime/postgresql/postgresql_config.sql +--- asterisk-20.7.0.orig/contrib/realtime/postgresql/postgresql_config.sql 2024-04-04 00:48:07.196695587 +1300 ++++ asterisk-20.7.0/contrib/realtime/postgresql/postgresql_config.sql 2024-04-04 00:48:46.675614904 +1300 +@@ -115,6 +115,9 @@ + dynamic yes_no_values, + path VARCHAR(256), + supportpath yes_no_values, ++ donotdisturb yes_no_values, ++ callforward VARCHAR(40), ++ huntgroup yes_no_values, + PRIMARY KEY (id), + UNIQUE (name) + ); +diff -durN asterisk-20.7.0.orig/include/asterisk/callerid.h asterisk-20.7.0/include/asterisk/callerid.h +--- asterisk-20.7.0.orig/include/asterisk/callerid.h 2024-04-04 00:48:07.204695368 +1300 ++++ asterisk-20.7.0/include/asterisk/callerid.h 2024-04-04 00:48:46.675614904 +1300 +@@ -543,7 +543,11 @@ + /*! Update from call transfer(active) (Party has already answered) */ + AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER, + /*! Update from call transfer(alerting) (Party has not answered yet) */ +- AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING ++ AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING, ++ /*! Update from a call being parked */ ++ AST_CONNECTED_LINE_UPDATE_SOURCE_PARKED_CALL, ++ /*! Update from a call joining a conference */ ++ AST_CONNECTED_LINE_UPDATE_SOURCE_CONFERENCE + }; + + /*! +diff -durN asterisk-20.7.0.orig/include/asterisk/parking.h asterisk-20.7.0/include/asterisk/parking.h +--- asterisk-20.7.0.orig/include/asterisk/parking.h 2024-04-04 00:48:07.204695368 +1300 ++++ asterisk-20.7.0/include/asterisk/parking.h 2024-04-04 00:48:46.675614904 +1300 +@@ -50,6 +50,7 @@ + PARKED_CALL_UNPARKED, + PARKED_CALL_FAILED, + PARKED_CALL_SWAP, ++ PARKED_CALL_REMINDER, + }; + + /*! +diff -durN asterisk-20.7.0.orig/main/callerid.c asterisk-20.7.0/main/callerid.c +--- asterisk-20.7.0.orig/main/callerid.c 2024-04-04 00:48:07.208695259 +1300 ++++ asterisk-20.7.0/main/callerid.c 2024-04-04 00:48:46.679614794 +1300 +@@ -1373,7 +1373,9 @@ + { AST_CONNECTED_LINE_UPDATE_SOURCE_DIVERSION, "diversion", "Call Diversion (Deprecated, use REDIRECTING)" }, + { AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER, "transfer_active", "Call Transfer(Active)" }, + { AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER, "transfer", "Call Transfer(Active)" },/* Old name must come after new name. */ +- { AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING, "transfer_alerting", "Call Transfer(Alerting)" } ++ { AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER_ALERTING, "transfer_alerting", "Call Transfer(Alerting)" }, ++ { AST_CONNECTED_LINE_UPDATE_SOURCE_PARKED_CALL, "parked_call", "Call Parked" }, ++ { AST_CONNECTED_LINE_UPDATE_SOURCE_CONFERENCE, "conference", "Conference" } + /* *INDENT-ON* */ + }; + +diff -durN asterisk-20.7.0.orig/main/cel.c asterisk-20.7.0/main/cel.c +--- asterisk-20.7.0.orig/main/cel.c 2024-04-04 00:48:07.208695259 +1300 ++++ asterisk-20.7.0/main/cel.c 2024-04-04 00:48:46.679614794 +1300 +@@ -1139,6 +1139,8 @@ + case PARKED_CALL_SWAP: + reason = "ParkedCallSwap"; + break; ++ case PARKED_CALL_REMINDER: ++ return; + } + + if (parked_payload->retriever) { +diff -durN asterisk-20.7.0.orig/main/pbx.c asterisk-20.7.0/main/pbx.c +--- asterisk-20.7.0.orig/main/pbx.c 2024-04-04 00:48:07.212695149 +1300 ++++ asterisk-20.7.0/main/pbx.c 2024-04-04 00:48:46.683614685 +1300 +@@ -5227,7 +5227,7 @@ + return CLI_SUCCESS; + } + /* ... we have hints ... */ +- ast_cli(a->fd, "\n -= Registered Asterisk Dial Plan Hints =-\n"); ++ ast_cli(a->fd, "Location Hints DeviceState PresenceState Watchers\n"); + + i = ao2_iterator_init(hints, 0); + for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) { +@@ -5254,8 +5254,7 @@ + } + ao2_iterator_destroy(&i); + +- ast_cli(a->fd, "----------------\n"); +- ast_cli(a->fd, "- %d hints registered\n", num); ++ ast_cli(a->fd, "%d hints registered\n", num); + return CLI_SUCCESS; + } + +diff -durN asterisk-20.7.0.orig/main/presencestate.c asterisk-20.7.0/main/presencestate.c +--- asterisk-20.7.0.orig/main/presencestate.c 2024-04-04 00:48:07.224694821 +1300 ++++ asterisk-20.7.0/main/presencestate.c 2024-04-04 00:48:46.683614685 +1300 +@@ -72,13 +72,13 @@ + enum ast_presence_state state; + + } state2string[] = { +- { "not_set", AST_PRESENCE_NOT_SET}, +- { "unavailable", AST_PRESENCE_UNAVAILABLE }, +- { "available", AST_PRESENCE_AVAILABLE}, +- { "away", AST_PRESENCE_AWAY}, +- { "xa", AST_PRESENCE_XA}, +- { "chat", AST_PRESENCE_CHAT}, +- { "dnd", AST_PRESENCE_DND}, ++ { "Not_Set", AST_PRESENCE_NOT_SET}, ++ { "Unavailable", AST_PRESENCE_UNAVAILABLE }, ++ { "Available", AST_PRESENCE_AVAILABLE}, ++ { "Away", AST_PRESENCE_AWAY}, ++ { "XA", AST_PRESENCE_XA}, ++ { "Chat", AST_PRESENCE_CHAT}, ++ { "DND", AST_PRESENCE_DND}, + }; + + static struct ast_manager_event_blob *presence_state_to_ami(struct stasis_message *msg); +diff -durN asterisk-20.7.0.orig/res/parking/parking_applications.c asterisk-20.7.0/res/parking/parking_applications.c +--- asterisk-20.7.0.orig/res/parking/parking_applications.c 2024-04-04 00:48:07.228694711 +1300 ++++ asterisk-20.7.0/res/parking/parking_applications.c 2024-04-04 00:48:46.683614685 +1300 +@@ -77,6 +77,11 @@ + Use a timeout of duration seconds instead + of the timeout specified by the parking lot. + ++ + + + +@@ -242,6 +247,7 @@ + OPT_ARG_COMEBACK, + OPT_ARG_TIMEOUT, + OPT_ARG_MUSICONHOLD, ++ OPT_ARG_REMINDER, + OPT_ARG_ARRAY_SIZE /* Always the last element of the enum */ + }; + +@@ -252,6 +258,7 @@ + MUXFLAG_COMEBACK_OVERRIDE = (1 << 3), + MUXFLAG_TIMEOUT_OVERRIDE = (1 << 4), + MUXFLAG_MUSICONHOLD = (1 << 5), ++ MUXFLAG_REMINDER_OVERRIDE = (1 << 6), + }; + + AST_APP_OPTIONS(park_opts, { +@@ -261,6 +268,7 @@ + AST_APP_OPTION_ARG('c', MUXFLAG_COMEBACK_OVERRIDE, OPT_ARG_COMEBACK), + AST_APP_OPTION_ARG('t', MUXFLAG_TIMEOUT_OVERRIDE, OPT_ARG_TIMEOUT), + AST_APP_OPTION_ARG('m', MUXFLAG_MUSICONHOLD, OPT_ARG_MUSICONHOLD), ++ AST_APP_OPTION_ARG('T', MUXFLAG_REMINDER_OVERRIDE, OPT_ARG_REMINDER), + }); + + static int apply_option_timeout (int *var, char *timeout_arg) +@@ -278,8 +286,23 @@ + return 0; + } + ++static int apply_option_reminder (int *var, char *reminder_arg) ++{ ++ if (ast_strlen_zero(reminder_arg)) { ++ ast_log(LOG_ERROR, "No duration value provided for the reminder ('T') option.\n"); ++ return -1; ++ } ++ ++ if (sscanf(reminder_arg, "%d", var) != 1 || *var < 0) { ++ ast_log(LOG_ERROR, "Duration value provided for timeout ('T') option must be 0 or greater.\n"); ++ return -1; ++ } ++ ++ return 0; ++} ++ + static int park_app_parse_data(const char *data, int *disable_announce, int *use_ringing, int *randomize, int *time_limit, +- char **comeback_override, char **lot_name, char **musicclass) ++ int *reminder_delay, char **comeback_override, char **lot_name, char **musicclass) + { + char *parse; + struct ast_flags flags = { 0 }; +@@ -302,6 +325,12 @@ + } + } + ++ if (ast_test_flag(&flags, MUXFLAG_REMINDER_OVERRIDE)) { ++ if (apply_option_reminder(reminder_delay, opts[OPT_ARG_REMINDER])) { ++ return -1; ++ } ++ } ++ + if (ast_test_flag(&flags, MUXFLAG_COMEBACK_OVERRIDE)) { + *comeback_override = ast_strdup(opts[OPT_ARG_COMEBACK]); + } +@@ -368,7 +397,7 @@ + ast_channel_unlock(chan); + } + +-static int setup_park_common_datastore(struct ast_channel *parkee, const char *parker_uuid, const char *comeback_override, int randomize, int time_limit, int silence_announce) ++static int setup_park_common_datastore(struct ast_channel *parkee, const char *parker_uuid, const char *comeback_override, int randomize, int time_limit, int reminder_delay, int silence_announce) + { + struct ast_datastore *datastore = NULL; + struct park_common_datastore *park_datastore; +@@ -420,6 +449,7 @@ + + park_datastore->randomize = randomize; + park_datastore->time_limit = time_limit; ++ park_datastore->reminder_delay = reminder_delay; + park_datastore->silence_announce = silence_announce; + + if (comeback_override) { +@@ -468,6 +498,7 @@ + + data_copy->randomize = data->randomize; + data_copy->time_limit = data->time_limit; ++ data_copy->reminder_delay = data->reminder_delay; + data_copy->silence_announce = data->silence_announce; + + if (data->comeback_override) { +@@ -491,7 +522,7 @@ + + static struct ast_bridge *park_common_setup2(struct ast_channel *parkee, struct ast_channel *parker, + const char *lot_name, const char *comeback_override, const char *musicclass, +- int use_ringing, int randomize, int time_limit, int silence_announcements) ++ int use_ringing, int randomize, int time_limit, int reminder_delay, int silence_announcements) + { + struct ast_bridge *parking_bridge; + RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup); +@@ -531,15 +562,16 @@ + ast_channel_set_bridge_role_option(parkee, "holding_participant", "moh_class", musicclass); + } + setup_park_common_datastore(parkee, ast_channel_uniqueid(parker), comeback_override, randomize, time_limit, +- silence_announcements); ++ reminder_delay, silence_announcements); + return parking_bridge; + } + + struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker, + const char *lot_name, const char *comeback_override, +- int use_ringing, int randomize, int time_limit, int silence_announcements) ++ int use_ringing, int randomize, int time_limit, int reminder_delay, int silence_announcements) + { +- return park_common_setup2(parkee, parker, lot_name, comeback_override, NULL, use_ringing, randomize, time_limit, silence_announcements); ++ return park_common_setup2(parkee, parker, lot_name, comeback_override, NULL, use_ringing, randomize, ++ time_limit, reminder_delay, silence_announcements); + } + + struct ast_bridge *park_application_setup(struct ast_channel *parkee, struct ast_channel *parker, const char *app_data, +@@ -548,17 +580,19 @@ + int use_ringing = 0; + int randomize = 0; + int time_limit = -1; ++ int reminder_delay = -1; + + RAII_VAR(char *, comeback_override, NULL, ast_free); + RAII_VAR(char *, lot_name_app_arg, NULL, ast_free); + RAII_VAR(char *, musicclass, NULL, ast_free); + + if (app_data) { +- park_app_parse_data(app_data, silence_announcements, &use_ringing, &randomize, &time_limit, &comeback_override, &lot_name_app_arg, &musicclass); ++ park_app_parse_data(app_data, silence_announcements, &use_ringing, &randomize, &time_limit, ++ &reminder_delay, &comeback_override, &lot_name_app_arg, &musicclass); + } + + return park_common_setup2(parkee, parker, lot_name_app_arg, comeback_override, musicclass, use_ringing, +- randomize, time_limit, silence_announcements ? *silence_announcements : 0); ++ randomize, time_limit, reminder_delay, silence_announcements ? *silence_announcements : 0); + + } + +diff -durN asterisk-20.7.0.orig/res/parking/parking_bridge.c asterisk-20.7.0/res/parking/parking_bridge.c +--- asterisk-20.7.0.orig/res/parking/parking_bridge.c 2024-04-04 00:48:07.228694711 +1300 ++++ asterisk-20.7.0/res/parking/parking_bridge.c 2024-04-04 00:48:46.683614685 +1300 +@@ -31,6 +31,7 @@ + #include "asterisk/term.h" + #include "asterisk/features.h" + #include "asterisk/bridge_internal.h" ++#include "asterisk/callerid.h" + + struct ast_bridge_parking + { +@@ -104,7 +105,7 @@ + * + * \note ao2_cleanup this reference when you are done using it or you'll cause leaks. + */ +-static struct parked_user *generate_parked_user(struct parking_lot *lot, struct ast_channel *chan, const char *parker_channel_name, const char *parker_dial_string, int use_random_space, int time_limit) ++static struct parked_user *generate_parked_user(struct parking_lot *lot, struct ast_channel *chan, const char *parker_channel_name, const char *parker_dial_string, int use_random_space, int time_limit, int reminder_delay) + { + struct parked_user *new_parked_user; + int preferred_space = -1; /* Initialize to use parking lot defaults */ +@@ -161,6 +162,7 @@ + + new_parked_user->start = ast_tvnow(); + new_parked_user->time_limit = (time_limit >= 0) ? time_limit : lot->cfg->parkingtime; ++ new_parked_user->reminder_delay = (reminder_delay >= 0) ? reminder_delay : lot->cfg->remindertime; + + if (parker_dial_string) { + new_parked_user->parker_dial_string = ast_strdup(parker_dial_string); +@@ -208,6 +210,8 @@ + struct ast_channel_snapshot *parker = NULL; + const char *parker_channel_name = NULL; + RAII_VAR(struct park_common_datastore *, park_datastore, NULL, park_common_datastore_free); ++ char lid_num[16]; ++ struct ast_party_connected_line connected; + + ast_bridge_base_v_table.push(&self->base, bridge_channel, swap); + +@@ -247,6 +251,7 @@ + ast_bridge_channel_unlock(swap); + + parking_set_duration(bridge_channel->features, pu); ++ parking_set_reminder(bridge_channel->features, pu); + + if (parking_channel_set_roles(bridge_channel->chan, self->lot, use_ringing)) { + ast_log(LOG_WARNING, "Failed to apply holding bridge roles to %s while joining the parking lot.\n", +@@ -286,7 +291,7 @@ + } + + pu = generate_parked_user(self->lot, bridge_channel->chan, parker_channel_name, +- park_datastore->parker_dial_string, park_datastore->randomize, park_datastore->time_limit); ++ park_datastore->parker_dial_string, park_datastore->randomize, park_datastore->time_limit, park_datastore->reminder_delay); + ao2_cleanup(parker); + if (!pu) { + publish_parked_call_failure(bridge_channel->chan); +@@ -311,6 +316,7 @@ + + /* Apply parking duration limits */ + parking_set_duration(bridge_channel->features, pu); ++ parking_set_reminder(bridge_channel->features, pu); + + /* Set this to the bridge pvt so that we don't have to refind the parked user associated with this bridge channel again. */ + bridge_channel->bridge_pvt = pu; +@@ -320,6 +326,21 @@ + COLORIZE(COLOR_BRMAGENTA, 0, self->lot->name), + pu->parking_space); + ++ snprintf(lid_num, sizeof(lid_num), "%d", pu->parking_space); ++ ast_party_connected_line_init(&connected); ++ ++ connected.id.name.str = ast_strdup("Park"); ++ connected.id.name.valid = 1; ++ ++ connected.id.number.str = ast_strdup(lid_num); ++ connected.id.number.valid = 1; ++ ++ connected.id.name.presentation = connected.id.number.presentation = AST_PRES_ALLOWED | AST_PRES_USER_NUMBER_PASSED_SCREEN; ++ connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_PARKED_CALL; ++ ++ ast_channel_update_connected_line(bridge_channel->chan, &connected, NULL); ++ ast_party_connected_line_free(&connected); ++ + parking_notify_metermaids(pu->parking_space, self->lot->cfg->parking_con, AST_DEVICE_INUSE); + + return 0; +diff -durN asterisk-20.7.0.orig/res/parking/parking_bridge_features.c asterisk-20.7.0/res/parking/parking_bridge_features.c +--- asterisk-20.7.0.orig/res/parking/parking_bridge_features.c 2024-04-04 00:48:07.228694711 +1300 ++++ asterisk-20.7.0/res/parking/parking_bridge_features.c 2024-04-04 00:48:46.687614575 +1300 +@@ -678,6 +678,15 @@ + return -1; + } + ++static int parking_reminder_callback(struct ast_bridge_channel *bridge_channel, void *hook_pvt) ++{ ++ struct parked_user *user = hook_pvt; ++ ++ publish_parked_call(user, PARKED_CALL_REMINDER); ++ ++ return -1; ++} ++ + void say_parking_space(struct ast_bridge_channel *bridge_channel, const char *payload) + { + unsigned int numeric_value; +@@ -726,6 +735,33 @@ + ao2_ref(user, -1); + } + } ++ ++void parking_set_reminder(struct ast_bridge_features *features, struct parked_user *user) ++{ ++ unsigned int reminder_delay; ++ ++ reminder_delay = user->reminder_delay * 1000; ++ ++ if (!reminder_delay) { ++ /* The is no reminder delay that we need to apply */ ++ return; ++ } ++ ++ /* If the reminder delay has already been passed skip it */ ++ reminder_delay = ast_remaining_ms(user->start, reminder_delay); ++ if (reminder_delay <= 0) { ++ return; ++ } ++ ++ /* The interval hook is going to need a reference to the parked_user */ ++ ao2_ref(user, +1); ++ ++ if (ast_bridge_interval_hook(features, 0, reminder_delay, ++ parking_reminder_callback, user, __ao2_cleanup, AST_BRIDGE_HOOK_REMOVE_ON_PULL)) { ++ ast_log(LOG_ERROR, "Failed to apply reminder delay to the parked call.\n"); ++ ao2_ref(user, -1); ++ } ++} + + /*! \brief Dial plan function to get the parking lot channel of an occupied parking lot */ + static int func_get_parkingslot_channel(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len) +diff -durN asterisk-20.7.0.orig/res/parking/parking_manager.c asterisk-20.7.0/res/parking/parking_manager.c +--- asterisk-20.7.0.orig/res/parking/parking_manager.c 2024-04-04 00:48:07.228694711 +1300 ++++ asterisk-20.7.0/res/parking/parking_manager.c 2024-04-04 00:48:46.687614575 +1300 +@@ -458,7 +458,7 @@ + struct ast_channel *chan, const char *parkinglot, int timeout_override) + { + struct ast_bridge *parking_bridge = park_common_setup(chan, +- chan, parkinglot, NULL, 0, 0, timeout_override, 1); ++ chan, parkinglot, NULL, 0, 0, timeout_override, -1, 1); + + if (!parking_bridge) { + astman_send_error(s, m, "Park action failed\n"); +@@ -667,6 +667,9 @@ + case PARKED_CALL_SWAP: + event_type = "ParkedCallSwap"; + break; ++ case PARKED_CALL_REMINDER: ++ /* PARKED_CALL_REMINDER doesn't currently get a message is is used exclusively for subscriptions */ ++ return; + case PARKED_CALL_FAILED: + /* PARKED_CALL_FAILED doesn't currently get a message and is used exclusively for bridging */ + return; +diff -durN asterisk-20.7.0.orig/res/parking/parking_ui.c asterisk-20.7.0/res/parking/parking_ui.c +--- asterisk-20.7.0.orig/res/parking/parking_ui.c 2024-04-04 00:48:07.232694601 +1300 ++++ asterisk-20.7.0/res/parking/parking_ui.c 2024-04-04 00:48:46.687614575 +1300 +@@ -58,6 +58,7 @@ + ast_cli(fd, "Parking Context : %s\n", lot->cfg->parking_con); + ast_cli(fd, "Parking Spaces : %d-%d\n", lot->cfg->parking_start, lot->cfg->parking_stop); + ast_cli(fd, "Parking Time : %u sec\n", lot->cfg->parkingtime); ++ ast_cli(fd, "Reminder Time : %u sec\n", lot->cfg->remindertime); + ast_cli(fd, "Comeback to Origin : %s\n", lot->cfg->comebacktoorigin ? "yes" : "no"); + ast_cli(fd, "Comeback Context : %s%s\n", lot->cfg->comebackcontext, lot->cfg->comebacktoorigin ? " (comebacktoorigin=yes, not used)" : ""); + ast_cli(fd, "Comeback Dial Time : %u sec\n", lot->cfg->comebackdialtime); +diff -durN asterisk-20.7.0.orig/res/parking/res_parking.h asterisk-20.7.0/res/parking/res_parking.h +--- asterisk-20.7.0.orig/res/parking/res_parking.h 2024-04-04 00:48:07.232694601 +1300 ++++ asterisk-20.7.0/res/parking/res_parking.h 2024-04-04 00:48:46.687614575 +1300 +@@ -67,6 +67,7 @@ + int parking_stop; /*!< Last space in the parking lot */ + + unsigned int parkingtime; /*!< Analogous to parkingtime config option */ ++ unsigned int remindertime; /*!< Analogous to remindertime config option */ + unsigned int comebackdialtime; /*!< Analogous to comebackdialtime config option */ + unsigned int parkfindnext; /*!< Analogous to parkfindnext config option */ + unsigned int parkext_exclusive; /*!< Analogous to parkext_exclusive config option */ +@@ -110,6 +111,7 @@ + char comeback[AST_MAX_CONTEXT]; /*!< Where to go on parking timeout */ + char *parker_dial_string; /*!< dialstring to call back with comebacktoorigin. Used timeout extension generation and call control */ + unsigned int time_limit; /*!< How long this specific channel may remain in the parking lot before timing out */ ++ unsigned int reminder_delay; /*!< How long to wait before sending a reminder */ + struct parking_lot *lot; /*!< Which parking lot the user is parked to */ + enum park_call_resolution resolution; /*!< How did the parking session end? If the call is in a bridge, lock parked_user before checking/setting */ + }; +@@ -268,6 +270,15 @@ + void parking_set_duration(struct ast_bridge_features *features, struct parked_user *user); + + /*! ++ * \since 13.7.2 ++ * \brief Setup a reminder delay feature an ast_bridge_features for parking ++ * ++ * \param features The ast_bridge_features we are establishing the interval hook on ++ * \param user The parked_user receiving the timeout duration limits ++ */ ++void parking_set_reminder(struct ast_bridge_features *features, struct parked_user *user); ++ ++/*! + * \since 12.0.0 + * \brief Get a pointer to the parking lot container for purposes such as iteration + * +@@ -422,7 +433,7 @@ + */ + struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker, + const char *lot_name, const char *comeback_override, +- int use_ringing, int randomize, int time_limit, int silence_announcements); ++ int use_ringing, int randomize, int time_limit, int reminder_delay, int silence_announcements); + + /*! + * \since 12.0.0 +@@ -451,6 +462,7 @@ + char *comeback_override; /*!< Optional goto string for where to send the call after we are done */ + int randomize; /*!< Pick a parking space to enter on at random */ + int time_limit; /*!< time limit override. -1 values don't override, 0 for unlimited time, >0 for custom time limit in seconds */ ++ int reminder_delay; /*!< reminder delay override. -1 values don't override, 0 for none, >0 custom reminder delay in seconds */ + int silence_announce; /*!< Used when a call parks itself to keep it from hearing the parked call announcement */ + }; + +diff -durN asterisk-20.7.0.orig/res/res_format_attr_h264.c asterisk-20.7.0/res/res_format_attr_h264.c +--- asterisk-20.7.0.orig/res/res_format_attr_h264.c 2024-04-04 00:48:07.232694601 +1300 ++++ asterisk-20.7.0/res/res_format_attr_h264.c 2024-04-04 00:48:46.687614575 +1300 +@@ -47,6 +47,7 @@ + * length. It must ALWAYS be a string literal representation of one less than + * H264_MAX_SPS_PPS_SIZE */ + #define H264_MAX_SPS_PPS_SIZE_SCAN_LIMIT "15" ++#define H264_MAX_IMAGEATTR_SIZE 256 + + struct h264_attr { + unsigned int PROFILE_IDC; +@@ -71,6 +72,7 @@ + unsigned int LEVEL_ASYMMETRY_ALLOWED; + char SPS[H264_MAX_SPS_PPS_SIZE]; + char PPS[H264_MAX_SPS_PPS_SIZE]; ++ char IMAGEATTR[H264_MAX_IMAGEATTR_SIZE]; + }; + + static void h264_destroy(struct ast_format *format) +@@ -160,6 +162,12 @@ + ast_copy_string(attr->PPS, attr2->PPS, sizeof(attr->PPS)); + } + ++ if (attr1 && !ast_strlen_zero(attr1->IMAGEATTR)) { ++ ast_copy_string(attr->IMAGEATTR, attr1->IMAGEATTR, sizeof(attr->IMAGEATTR)); ++ } else if (attr2 && !ast_strlen_zero(attr2->IMAGEATTR)) { ++ ast_copy_string(attr->IMAGEATTR, attr2->IMAGEATTR, sizeof(attr->IMAGEATTR)); ++ } ++ + return cloned; + } + +@@ -307,6 +315,42 @@ + return; + } + ++static struct ast_format *h264_attribute_set(const struct ast_format *format, const char *name, const char *value) ++{ ++ struct ast_format *cloned = ast_format_clone(format); ++ struct h264_attr *attr; ++ ++ if (!cloned) { ++ return NULL; ++ } ++ attr = ast_format_get_attribute_data(cloned); ++ ++ if (!strcmp(name, "imageattr")) { ++ ast_copy_string(attr->IMAGEATTR, value, sizeof(attr->IMAGEATTR)); ++ } else { ++ ast_log(LOG_WARNING, "unknown attribute type %s\n", name); ++ } ++ ++ return cloned; ++} ++ ++static const void *h264_attribute_get(const struct ast_format *format, const char *name) ++{ ++ struct h264_attr *attr = ast_format_get_attribute_data(format); ++ ++ if (!attr) { ++ return NULL; ++ } ++ ++ if (!strcmp(name, "imageattr")) { ++ return attr->IMAGEATTR; ++ } else { ++ ast_log(LOG_WARNING, "unknown attribute type %s\n", name); ++ } ++ ++ return NULL; ++} ++ + static struct ast_format_interface h264_interface = { + .format_destroy = h264_destroy, + .format_clone = h264_clone, +@@ -314,6 +358,8 @@ + .format_get_joint = h264_getjoint, + .format_parse_sdp_fmtp = h264_parse_sdp_fmtp, + .format_generate_sdp_fmtp = h264_generate_sdp_fmtp, ++ .format_attribute_set = h264_attribute_set, ++ .format_attribute_get = h264_attribute_get, + }; + + static int unload_module(void) +diff -durN asterisk-20.7.0.orig/res/res_parking.c asterisk-20.7.0/res/res_parking.c +--- asterisk-20.7.0.orig/res/res_parking.c 2024-04-04 00:48:07.232694601 +1300 ++++ asterisk-20.7.0/res/res_parking.c 2024-04-04 00:48:46.691614465 +1300 +@@ -113,6 +113,9 @@ + + Amount of time a call will remain parked before giving up (in seconds). + ++ ++ Amount of time before sending a reminder warning (in seconds). ++ + + Which music class to use for parked calls. They will use the default if unspecified. + +@@ -954,6 +957,7 @@ + cfg->parking_start = source->parking_start; + cfg->parking_stop = source->parking_stop; + cfg->parkingtime = source->parkingtime; ++ cfg->remindertime = source->remindertime; + cfg->comebackdialtime = source->comebackdialtime; + cfg->parkfindnext = source->parkfindnext; + cfg->parkext_exclusive = source->parkext_exclusive; +@@ -1226,6 +1230,7 @@ + aco_option_register(&cfg_info, "parkext", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parkext)); + aco_option_register(&cfg_info, "context", ACO_EXACT, parking_lot_types, "parkedcalls", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parking_con)); + aco_option_register(&cfg_info, "parkingtime", ACO_EXACT, parking_lot_types, "45", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, parkingtime)); ++ aco_option_register(&cfg_info, "remindertime", ACO_EXACT, parking_lot_types, "0", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, remindertime)); + aco_option_register(&cfg_info, "comebacktoorigin", ACO_EXACT, parking_lot_types, "yes", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, comebacktoorigin)); + aco_option_register(&cfg_info, "comebackcontext", ACO_EXACT, parking_lot_types, "parkedcallstimeout", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, comebackcontext)); + aco_option_register(&cfg_info, "comebackdialtime", ACO_EXACT, parking_lot_types, "30", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, comebackdialtime));