diff -durN asterisk-13.38.2.orig/channels/chan_sip.c asterisk-13.38.2/channels/chan_sip.c
--- asterisk-13.38.2.orig/channels/chan_sip.c	2021-04-22 22:53:09.782425011 +1200
+++ asterisk-13.38.2/channels/chan_sip.c	2021-04-22 22:55:52.266599165 +1200
@@ -360,6 +360,67 @@
 			application is only available if TEST_FRAMEWORK is defined.</para>
 		</description>
 	</application>
+	<application name="SIPCiscoPage" language="en_US">
+		<synopsis>
+			Page a series of Cisco USECALLMANAGER phones
+		</synopsis>
+		<syntax>
+			<parameter name="peers" required="true" argsep="&amp;">
+				<argument name="peer" required="true">
+					<para>Name of the SIP peer to page</para>
+				</argument>
+				<argument name="peer2" multiple="true">
+					<para>Name of the second peer to page, additional peers are
+					specified as peer&amp;peer2&amp;peer3...</para>
+				</argument>
+			</parameter>
+			<parameter name="options" required="false">
+				<optionlist>
+				<option name="m">
+					<argument name="x" required="true">
+						<para>Multicast IP address</para>
+					</argument>
+					<para>Use a single multicast RTP stream instead of
+					multiple unicast RTP streams.</para>
+				</option>
+				<option name="p">
+					<argument name="x" required="true">
+						<para>Port number. Must be an even number between
+						20480 (default) and 32768</para>
+					</argument>
+					<para>Port the phone will use to receive the audio</para>
+				</option>
+				<option name="b">
+					<para>Page phone even if it busy (off-hook, ringing, busy
+					or do-not-disturb)</para>
+				</option>
+				<option name="v">
+					<argument name="x" required="true">
+						<para>Volume. Must be a number between 1 and 100</para>
+					</argument>
+					<para>Force the receive volume on the phone</para> 
+				</option>
+				<option name="d">
+					<argument name="x" required="true">
+						<para>Message to display</para>
+					</argument>
+					<para>Display a message in status line of the phone</para>
+				</option>
+				<option name="o">
+					<para>Mark the phones as off-hook during paging.</para>
+				</option>
+				<option name="a">
+					<para>Play an alert beep on the paged phone.</para>
+				</option>
+				</optionlist>
+			</parameter>
+		</syntax>
+		<description>
+			<para>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.</para> 
+		</description>
+	</application>
 	<function name="SIP_HEADER" language="en_US">
 		<synopsis>
 			Gets the specified SIP header from an incoming INVITE message.
@@ -458,6 +519,27 @@
 					<enum name="codec[x]">
 						<para>Preferred codec index number <replaceable>x</replaceable> (beginning with zero).</para>
 					</enum>
+					<enum name="vmexten">
+						<para>The vmexten for this peer.</param>
+					</enum>
+					<enum name="donotdisturb">
+						<para>Is DoNotDisturb set on this peer (yes/no).</para>
+					</enum>
+					<enum name="callforward">
+						<para>The call forwarding extension for this peer.</para>
+					</enum>
+					<enum name="huntgroup">
+						<para>Is HuntGroup login set on this peer (yes/no).</para>
+					</enum>
+					<enum name="regcallid">
+						<para>The Call-ID of the REGISTER dialog.</para>
+					</enum>
+					<enum name="ciscodevicename">
+						<para>The device name of the Cisco USECALLMANAGER peer</para>
+					</enum>
+					<enum name="ciscolineindex">
+						<para>The line index of the Cisco USECALLMANAGER peer</para>
+					</enum>
 				</enumlist>
 			</parameter>
 		</syntax>
@@ -673,7 +755,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
@@ -837,6 +921,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 named ACL system change events */
 
 static char used_context[AST_MAX_CONTEXT];        /*!< name of automatically created context for unloading */
 
@@ -871,6 +956,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,      "<none>" },
 	{ REFER_SENT,      "Request sent" },
@@ -924,11 +1071,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
 
 /*!
@@ -951,6 +1104,7 @@
 } event_state_compositors [] = {
 #ifdef HAVE_LIBXML2
 	{CALL_COMPLETION, "call-completion", &cc_esc_publish_callbacks},
+	{PRESENCE,        "presence",        &presence_esc_publish_callbacks},
 #endif
 };
 
@@ -1016,6 +1170,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);
 
@@ -1121,6 +1279,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);
@@ -1146,7 +1305,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_security_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);
@@ -1156,6 +1316,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);
@@ -1169,6 +1330,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);
@@ -1178,7 +1340,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);
@@ -1193,6 +1359,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);
@@ -1203,6 +1370,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);
@@ -1216,9 +1384,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);
@@ -1260,7 +1428,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);
@@ -1282,6 +1450,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)
 
@@ -1348,6 +1517,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);
@@ -1358,7 +1529,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);
@@ -1412,9 +1583,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);
@@ -1461,7 +1637,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);
@@ -1526,6 +1702,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 */
@@ -3348,9 +3525,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) {
@@ -5249,6 +5440,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);
@@ -5265,6 +5535,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);
@@ -5282,6 +5555,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;
@@ -5949,6 +6232,29 @@
 	}
 }
 
+static void copy_pvt_data(struct sip_pvt *to_pvt, const struct sip_pvt *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);
+}
+
 /*! \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)
 {
@@ -6245,6 +6551,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;
 }
@@ -6415,6 +6725,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) {
@@ -6637,6 +6956,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");
 
@@ -7180,6 +7502,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 */
@@ -8069,6 +8396,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 */
@@ -8541,14 +8869,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;
@@ -8556,8 +8882,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;
 		}
 	}
@@ -8565,6 +8891,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)
 {
@@ -10262,7 +10650,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 */
@@ -10330,11 +10720,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;
@@ -10787,7 +11177,7 @@
 					} else if (!processed_crypto && process_crypto(p, p->rtp, &p->srtp, value, secure_audio)) {
 						processed_crypto = TRUE;
 						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_rtcp_mux(value, p, &remote_rtcp_mux_audio)) {
 						processed = TRUE;
@@ -10806,7 +11196,7 @@
 					} else if (!processed_crypto && process_crypto(p, p->vrtp, &p->vsrtp, value, secure_video)) {
 						processed_crypto = TRUE;
 						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_rtcp_mux(value, p, &remote_rtcp_mux_video)) {
 						processed = TRUE;
@@ -10816,7 +11206,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, 1)) {
 						processed_crypto = TRUE;
@@ -11488,11 +11878,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);
@@ -11515,24 +11905,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;
@@ -11568,36 +11958,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;
@@ -11617,41 +12008,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;
@@ -11792,15 +12203,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-7.0.0");
+	}
 
-	return res;
+	return add_header(req, "Supported", ast_str_buffer(supported));
 }
 
 /*! \brief Add header to SIP message */
@@ -12459,8 +12875,9 @@
 		ast_clear_flag(&p->flags[1], SIP_PAGE2_CONNECTLINEUPDATE_PEND);
 		add_rpid(&resp, p);
 	}
+	add_security_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 */
@@ -12707,6 +13124,35 @@
 	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, "<x-cisco-remotecc-response>\n");
+	add_content(&resp, "<response>\n");
+	add_content(&resp, "<code>200</code>\n");
+	add_content(&resp, "<optionsind>\n");
+	add_content(&resp, "<combine max=\"6\">\n");
+	add_content(&resp, "<remotecc><status /></remotecc>\n");
+	add_content(&resp, "<service-control />\n");
+	add_content(&resp, "</combine>\n");
+	add_content(&resp, "<dialog usage=\"hook status\"><unot /></dialog>\n");
+	add_content(&resp, "<dialog usage=\"shared line\"><unot /></dialog>\n");
+	add_content(&resp, "<presence usage=\"blf speed dial\"><unot /></presence>\n");
+	add_content(&resp, "<joinreq></joinreq>\n");
+	add_content(&resp, "</optionsind>\n");
+	add_content(&resp, "</response>\n");
+	add_content(&resp, "</x-cisco-remotecc-response>\n");
+
+	return send_response(p, &resp, XMIT_UNRELIABLE, 0);
+}
+
 /*!
  \brief Extract domain from SIP To/From header
  \return -1 on error, 1 if domain string is empty, 0 if domain was properly extracted
@@ -12931,15 +13377,14 @@
 {
 	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;
 	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\" <sip:anonymous@anonymous.invalid>";
 
 	if (!ast_test_flag(&p->flags[0], SIP_SENDRPID)) {
 		return 0;
@@ -12950,16 +13395,12 @@
 	}
 	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 */
@@ -12975,67 +13416,67 @@
 		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)) {
+		/* Phone will translate \2004 into the localised version of Conference and enable ConfList/ConfDetails support */
+		if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && !strcmp(lid_name, "Conference") && ast_strlen_zero(lid_num) && p->conference) {
+			lid_name = "\2004";
+		}
+
+		ast_escape_quoted(lid_name, tmp2, sizeof(tmp2));
+		ast_str_append(&tmp, -1, "\"%s\" ", tmp2);
+	}
+	ast_str_append(&tmp, -1, "<sip:");
+	if (!ast_strlen_zero(lid_num)) {
+		ast_uri_encode(lid_num, tmp2, sizeof(tmp2), ast_uri_sip_user);
+		ast_str_append(&tmp, -1, "%s@", tmp2);
+	}
+	ast_str_append(&tmp, -1, "%s", fromdomain);
+	callback_num = pbx_builtin_getvar_helper(p->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\" <sip:%s@%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\" <sip:%s@%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\" <sip:anonymous@anonymous.invalid>");
 			}
 		}
 		add_header(req, "P-Asserted-Identity", ast_str_buffer(tmp));
 	} else {
-		ast_str_set(&tmp, -1, "\"%s\" <sip:%s@%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));
+
+		if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) {
+			ast_str_reset(tmp);
+			ast_str_append(&tmp, -1, "<urn:x-cisco-remotecc:callinfo>; orientation=%s", p->outgoing_call ? "from" : "to");
+
+			add_header(req, "Call-Info", ast_str_buffer(tmp));
+		}
 	}
 	return 0;
 }
@@ -13255,6 +13696,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) {
@@ -13262,6 +13709,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 */
@@ -13625,7 +14082,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));
@@ -13665,6 +14122,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 */
@@ -14033,7 +14495,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);
@@ -14065,6 +14527,29 @@
 	ao2_ref(agent, -1);
 }
 
+static void add_security_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, "<urn:x-cisco-remotecc:callinfo>; security=");
+
+	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");
+	}
+
+	add_header(req, "Call-Info", ast_str_buffer(header));
+}
+
 /*! \brief Used for 200 OK and 183 early media
 	\return Will return XMIT_ERROR for network errors.
 */
@@ -14080,8 +14565,9 @@
 	if (rpid == TRUE) {
 		add_rpid(&resp, p);
 	}
+	add_security_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);
@@ -14366,8 +14852,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;
 
@@ -14417,7 +14901,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.
 	 */
@@ -14426,13 +14910,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))
@@ -14475,7 +14954,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) {
@@ -14651,9 +15130,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;
@@ -14720,12 +15235,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");
@@ -14817,6 +15345,8 @@
 		add_rpid(&req, p);
 	if (sipmethod == SIP_INVITE) {
 		add_diversion(&req, p);
+		add_join(&req, p);
+		add_security_call_info(&req, p);
 	}
 	if (sdp) {
 		offered_media_list_destroy(p);
@@ -15112,6 +15642,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)) {
@@ -15130,93 +15663,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) ? "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:
@@ -15227,46 +15676,91 @@
 		ast_str_append(tmp, 0, "<presentity uri=\"%s;method=SUBSCRIBE\" />\n", mfrom);
 		ast_str_append(tmp, 0, "<atom id=\"%s\">\n", exten);
 		ast_str_append(tmp, 0, "<address uri=\"%s;user=ip\" priority=\"0.800000\">\n", mto);
-		ast_str_append(tmp, 0, "<status status=\"%s\" />\n", (local_state ==  NOTIFY_OPEN) ? "open" : (local_state == NOTIFY_INUSE) ? "inuse" : "closed");
-		ast_str_append(tmp, 0, "<msnsubstatus substatus=\"%s\" />\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, "<status status=\"inuse\" />\n");
+			ast_str_append(tmp, 0, "<msnsubstatus substatus=\"onthephone\" />\n");
+		} else if (data->state & (AST_EXTENSION_BUSY | AST_EXTENSION_UNAVAILABLE | AST_EXTENSION_ONHOLD)) {
+			ast_str_append(tmp, 0, "<status status=\"closed\" />\n");
+			ast_str_append(tmp, 0, "<msnsubstatus substatus=\"offline\" />\n");
+		} else {
+			ast_str_append(tmp, 0, "<status status=\"open\" />\n");
+			ast_str_append(tmp, 0, "<msnsubstatus substatus=\"online\" />\n");
+		}
 		ast_str_append(tmp, 0, "</address>\n</atom>\n</presence>\n");
 		break;
 	case PIDF_XML: /* Eyebeam supports this format */
-		ast_str_append(tmp, 0,
-			"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
-			"<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" \nxmlns:pp=\"urn:ietf:params:xml:ns:pidf:person\"\nxmlns:es=\"urn:ietf:params:xml:ns:pidf:rpid:status:rpid-status\"\nxmlns:ep=\"urn:ietf:params:xml:ns:pidf:rpid:rpid-person\"\nentity=\"%s\">\n", mfrom);
-		ast_str_append(tmp, 0, "<pp:person><status>\n");
-		if (pidfstate[0] != '-') {
-			ast_str_append(tmp, 0, "<ep:activities><ep:%s/></ep:activities>\n", pidfstate);
-		}
-		ast_str_append(tmp, 0, "</status></pp:person>\n");
-		ast_str_append(tmp, 0, "<note>%s</note>\n", pidfnote); /* Note */
-		ast_str_append(tmp, 0, "<tuple id=\"%s\">\n", exten); /* Tuple start */
-		ast_str_append(tmp, 0, "<contact priority=\"1\">%s</contact>\n", mto);
-		if (pidfstate[0] == 'b') /* Busy? Still open ... */
-			ast_str_append(tmp, 0, "<status><basic>open</basic></status>\n");
-		else
-			ast_str_append(tmp, 0, "<status><basic>%s</basic></status>\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,
+				"<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" xmlns:dm=\"urn:ietf:params:xml:ns:pidf:data-model\" xmlns:e=\"urn:ietf:params:xml:ns:pidf:status:rpid\" xmlns:ce=\"urn:cisco:params:xml:ns:pidf:rpid\" entity=\"%s\">\n", mfrom);
+			ast_str_append(tmp, 0, "<dm:person>\n");
+			ast_str_append(tmp, 0, "<e:activities>\n");
+			if (data->state & AST_EXTENSION_RINGING && sip_cfg.notifyringing) {
+				ast_str_append(tmp, 0, "<ce:alerting />\n");
+			} else if (data->state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY | AST_EXTENSION_ONHOLD)) {
+				ast_str_append(tmp, 0, "<e:on-the-phone />\n");
+			} else if (data->presence_state == AST_PRESENCE_DND) {
+				ast_str_append(tmp, 0, "<ce:dnd />\n");
+			}
+			ast_str_append(tmp, 0, "</e:activities>\n");
+			ast_str_append(tmp, 0, "</dm:person>\n");
+			ast_str_append(tmp, 0, "<tuple id=\"%d\">\n", p->dialogver);
+			if (data->state == AST_EXTENSION_UNAVAILABLE) {
+				ast_str_append(tmp, 0, "<status><basic>closed</basic></status>\n");
+			} else {
+				ast_str_append(tmp, 0, "<status><basic>open</basic></status>\n");
+			}
 			ast_str_append(tmp, 0, "</tuple>\n");
-			ast_str_append(tmp, 0, "<tuple id=\"digium-presence\">\n");
-			ast_str_append(tmp, 0, "<status>\n");
-			ast_str_append(tmp, 0, "<digium_presence type=\"%s\" subtype=\"%s\">%s</digium_presence>\n",
-				ast_presence_state2str(data->presence_state),
-				S_OR(data->presence_subtype, ""),
-				S_OR(data->presence_message, ""));
-			ast_str_append(tmp, 0, "</status>\n");
-			ast_test_suite_event_notify("DIGIUM_PRESENCE_SENT",
+			ast_str_append(tmp, 0, "</presence>\n");
+		} else {
+			ast_str_append(tmp, 0,
+				"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
+				"<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" \nxmlns:pp=\"urn:ietf:params:xml:ns:pidf:person\"\nxmlns:es=\"urn:ietf:params:xml:ns:pidf:rpid:status:rpid-status\"\nxmlns:ep=\"urn:ietf:params:xml:ns:pidf:rpid:rpid-person\"\nentity=\"%s\">\n", mfrom);
+			ast_str_append(tmp, 0, "<pp:person><status>\n");
+			if (data->state == AST_EXTENSION_UNAVAILABLE) {
+				ast_str_append(tmp, 0, "<ep:activities><ep:away /></ep:activities>\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, "<ep:activities><ep:busy /></ep:activities>\n");
+			}
+			ast_str_append(tmp, 0, "</status></pp:person>\n");
+			if (data->state & AST_EXTENSION_RINGING) {
+				ast_str_append(tmp, 0, "<note>Ringing</note>\n");
+			} else if (data->state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY)) {
+				ast_str_append(tmp, 0, "<note>On the phone</note>\n");
+			} else if (data->state == AST_EXTENSION_ONHOLD) {
+				ast_str_append(tmp, 0, "<note>On hold</note>\n");
+			} else if (data->state == AST_EXTENSION_UNAVAILABLE) {
+				ast_str_append(tmp, 0, "<note>Unavailable</note>\n");
+			} else if (data->presence_state == AST_PRESENCE_DND) {
+				ast_str_append(tmp, 0, "<note>Do not disturb</note>\n");
+			} else {
+				ast_str_append(tmp, 0, "<note>Ready</note>\n");
+			}
+			ast_str_append(tmp, 0, "<tuple id=\"%s\">\n", exten); /* Tuple start */
+			ast_str_append(tmp, 0, "<contact priority=\"1\">%s</contact>\n", mto);
+			if (data->state & (AST_EXTENSION_UNAVAILABLE | AST_EXTENSION_BUSY | AST_EXTENSION_ONHOLD)) {
+				ast_str_append(tmp, 0, "<status><basic>closed</basic></status>\n");
+			} else {
+				ast_str_append(tmp, 0, "<status><basic>open</basic></status>\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, "</tuple>\n");
+				ast_str_append(tmp, 0, "<tuple id=\"digium-presence\">\n");
+				ast_str_append(tmp, 0, "<status>\n");
+				ast_str_append(tmp, 0, "<digium_presence type=\"%s\" subtype=\"%s\">%s</digium_presence>\n",
+					ast_presence_state2str(data->presence_state),
+					S_OR(data->presence_subtype, ""),
+					S_OR(data->presence_message, ""));
+				ast_str_append(tmp, 0, "</status>\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, "</tuple>\n</presence>\n");
 		}
-		ast_str_append(tmp, 0, "</tuple>\n</presence>\n");
 		break;
 	case DIALOG_INFO_XML: /* SNOM subscribes in this format */
 		ast_str_append(tmp, 0, "<?xml version=\"1.0\"?>\n");
@@ -15376,7 +15870,13 @@
 		} else {
 			ast_str_append(tmp, 0, "<dialog id=\"%s\">\n", exten);
 		}
-		ast_str_append(tmp, 0, "<state>%s</state>\n", statestring);
+		if (data->state & AST_EXTENSION_RINGING && sip_cfg.notifyringing) {
+			ast_str_append(tmp, 0, "<state>early</state>\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, "<state>confirmed</state>\n");
+		} else {
+			ast_str_append(tmp, 0, "<state>terminated</state>\n");
+		}
 		if (data->state == AST_EXTENSION_ONHOLD) {
 				ast_str_append(tmp, 0, "<local>\n<target uri=\"%s\">\n"
 			                                    "<param pname=\"+sip.rendering\" pvalue=\"no\"/>\n"
@@ -15471,36 +15971,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));
 
@@ -15561,7 +16047,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) {
@@ -15720,10 +16206,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",
@@ -15733,7 +16215,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);
@@ -15745,11 +16232,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);
@@ -15778,6 +16260,159 @@
 	}
 }
 
+/*! \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, NULL))) {
+			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, "<x-cisco-remotecc-request>\n");
+		ast_str_append(&content, 0, "<dndupdate>\n");
+		ast_str_append(&content, 0, "<state>%s</state>\n", peer->donotdisturb ? "enable" : "disable");
+		ast_str_append(&content, 0, "<option>%s</option>\n", ast_test_flag(&pvt->flags[2], SIP_PAGE3_DND_BUSY) ? "callreject" : "ringeroff");
+		ast_str_append(&content, 0, "</dndupdate>\n");
+		ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, "<?xml version=\"1.0\"?>\n");
+		add_content(&req, "<DoNotDisturbEvent xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">\n");
+		snprintf(tmp, sizeof(tmp), "<device><notKnown /></device>\n<doNotDisturbOn>%s</doNotDisturbOn>\n", peer->donotdisturb ? "true" : "false");
+		add_content(&req, tmp);
+		add_content(&req, "</DoNotDisturbEvent>\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, NULL))) {
+			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, "<x-cisco-remotecc-request>\n");
+		ast_str_append(&content, 0, "<hlogupdate>\n");
+		ast_str_append(&content, 0, "<status>%s</status>\n", peer->huntgroup ? "on" : "off");
+		ast_str_append(&content, 0, "</hlogupdate>\n");
+		ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, NULL))) {
+			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, "<x-cisco-remotecc-request>\n");
+		ast_str_append(&content, 0, "<cfwdallupdate>\n");
+		ast_str_append(&content, 0, "<linenumber>%d</linenumber>\n", peer->cisco_lineindex);
+		ast_str_append(&content, 0, "<fwdaddress>%s</fwdaddress>\n", peer->callforward);
+		ast_str_append(&content, 0, "<tovoicemail>%s</tovoicemail>\n", !ast_strlen_zero(peer->vmexten) && !strcmp(peer->callforward, peer->vmexten) ? "on" : "off");
+		ast_str_append(&content, 0, "</cfwdallupdate>\n");
+		ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, "<?xml version=\"1.0\"?>\n");
+		add_content(&req, "<ForwardingEvent xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">\n");
+		add_content(&req, "<device><notKnown /></device>\n<forwardingType>forwardImmediate</forwardingType>\n");
+		snprintf(tmp, sizeof(tmp), "<forwardStatus>%s</forwardStatus>\n<forwardTo>%s</forwardTo>\n", !ast_strlen_zero(peer->callforward) ? "true" : "false", peer->callforward);
+		add_content(&req, tmp);
+		add_content(&req, "</ForwardingEvent>\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"},
@@ -16191,8 +16826,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 and port so the correct contact is built */
@@ -16329,6 +16964,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;
 }
 
@@ -16337,6 +16975,7 @@
 {
 	if (p->refer) {
 		ast_string_field_free_memory(p->refer);
+		ast_free(p->refer->content);
 		ast_free(p->refer);
 		p->refer = NULL;
 	}
@@ -16416,6 +17055,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)
 {
@@ -16498,6 +17159,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);
@@ -16604,6 +17269,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;
@@ -16611,6 +17277,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);
@@ -16623,6 +17290,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);
@@ -16655,6 +17330,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");
 
@@ -16873,7 +17549,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];
@@ -16950,6 +17626,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"));
@@ -17087,15 +17764,34 @@
 	}
 
 	/* 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 *devicename;
+
+		ast_copy_string(contact, sip_get_header(req, "Contact"), sizeof(contact));
+		devicename = strcasestr(contact, ";+u.sip!devicename.ccm.cisco.com=\"");
+
+		if (!ast_strlen_zero(devicename)) {
+			devicename += 34;
+			devicename = strsep(&devicename, "\"");
+			ast_string_field_set(peer, cisco_devicename, devicename);
+		} 
+	}
+
 	/* Save User agent */
 	useragent = sip_get_header(req, "User-Agent");
 	if (strcasecmp(useragent, peer->useragent)) {
@@ -17282,6 +17978,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 */
@@ -17455,7 +18153,7 @@
 	}
 
 	if (ast_mwi_state_type() == stasis_message_type(msg)) {
-		sip_send_mwi_to_peer(peer, 0);
+		sip_send_mwi(peer, 0);
 	}
 }
 
@@ -17512,6 +18210,218 @@
 	}
 }
 
+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, NULL)))) {
+			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) || ast_test_flag(&peer->flags[2], 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, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+			ast_str_append(&content, 0, "<x-cisco-remotecc-request>\n");
+			ast_str_append(&content, 0, "<statuslineupdatereq>\n");
+			ast_str_append(&content, 0, "<action>notify_display</action>\n");
+			ast_str_append(&content, 0, "<statustext>");
+			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, "</statustext>\n");
+			ast_str_append(&content, 0, "<displaytimeout>%d</displaytimeout>\n", peer->cisco_pickupnotify_timer);
+			ast_str_append(&content, 0, "<linenumber>0</linenumber>\n");
+			ast_str_append(&content, 0, "<priority>1</priority>\n");
+			ast_str_append(&content, 0, "</statuslineupdatereq>\n");
+			ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, "<x-cisco-remotecc-request>\n");
+			ast_str_append(&content, 0, "<playtonereq>\n");
+			ast_str_append(&content, 0, "<tonetype>DtZipZip</tonetype>\n");
+			ast_str_append(&content, 0, "<direction>all</direction>\n");
+			ast_str_append(&content, 0, "</playtonereq>\n");
+			ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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;
@@ -17539,8 +18449,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;
@@ -17592,7 +18502,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)" : "");
 	}
 
@@ -17615,12 +18525,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, &notify_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, &notify_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, NULL))) {
+				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; /* XXX 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, &notify_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
@@ -17781,7 +18805,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);
 
@@ -17851,6 +18875,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) {
@@ -17890,7 +18924,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);
@@ -17905,7 +18939,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);
@@ -17913,9 +18946,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(&p->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;
 					}
@@ -17939,7 +18977,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);
@@ -17953,13 +18991,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(&p->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);
@@ -17968,7 +19010,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;
 			}
@@ -17976,10 +19018,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);
 		}
@@ -18098,82 +19149,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.
@@ -18188,6 +19163,7 @@
 	char *privacy = "";
 	char *screen = "";
 	char *start, *end;
+	int pai;
 
 	if (!ast_test_flag(&p->flags[0], SIP_TRUSTRPID))
 		return 0;
@@ -18196,7 +19172,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;
 	}
 
 	/*
@@ -18248,7 +19230,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;
@@ -18588,6 +19576,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
@@ -19138,10 +20134,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);
@@ -19209,6 +20205,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);
@@ -19236,12 +20233,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
@@ -19609,7 +20611,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) {
@@ -21037,7 +22039,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;
 
@@ -21070,6 +22072,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");
 
@@ -21150,7 +22154,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);
@@ -21229,9 +22233,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));
@@ -21243,6 +22247,26 @@
 		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);
+
+			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 */
@@ -21279,7 +22303,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);
@@ -21338,6 +22362,17 @@
 		}
 		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");
+		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);
+		}
+		astman_append(s, "CiscoDevicename: %s\r\n", peer->cisco_devicename);
+		astman_append(s, "CiscoLineIndex: %d\r\n", peer->cisco_lineindex);
 
 		peer = sip_unref_peer(peer, "sip_show_peer: sip_unref_peer: done with peer");
 
@@ -21453,9 +22488,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");
@@ -22059,7 +23094,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,
@@ -22533,7 +23568,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 */
@@ -22761,6 +23796,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) {
@@ -22790,10 +23827,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, NULL))) {
 			ast_log(LOG_WARNING, "Unable to build sip pvt data for notify (memory/socket error)\n");
@@ -22812,36 +23860,214 @@
 		/* 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} <peer>\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");
+	}
+	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} <peer>\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");
+	}
+	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 <peer> <extension>|off <peer>}\n"
+			"       Sets/Removes 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' removed\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);
+		}
+	}
+	sip_send_callforward(peer);
+	peer = sip_unref_peer(peer, "unref after sip_find_peer");
+
 	return CLI_SUCCESS;
 }
 
@@ -23193,7 +24419,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;
@@ -23247,7 +24473,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);
@@ -23288,11 +24514,82 @@
 		} 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");
+		}
+		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");
+		}
+		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);
+			}
+		}
+		sip_send_callforward(peer);
+	}
+
+	sip_unref_peer(peer, "sip_unref_peer from function_sippeer_write, just before return");
 
 	return 0;
 }
@@ -23300,7 +24597,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
@@ -24161,6 +25459,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 */
@@ -24493,6 +25805,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 */
@@ -24744,6 +26057,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.)
@@ -24767,26 +26082,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);
 		}
@@ -24796,6 +26109,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(ast_check_realtime("sipregs") ? "sipregs" : "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 */
@@ -25342,6 +26687,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;
@@ -25440,6 +26788,2237 @@
 	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");
+	}
+	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);
+
+		ast_channel_lock(chan);
+		ast_party_connected_line_copy(&connected, ast_channel_connected(chan));
+		ast_channel_unlock(chan);
+
+		ast_free(connected.id.name.str);
+		connected.id.name.str = ast_strdup("Conference");
+		connected.id.name.valid = 1;
+
+		ast_free(connected.id.number.str);
+		connected.id.number.str = ast_strdup("");
+		connected.id.number.valid = 1;
+
+		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, NULL))) {
+			goto conference_cleanup;
+		}
+		copy_pvt_data(pvt, conference_data->pvt);
+
+		ast_str_append(&content, 0, "<x-cisco-remotecc-request>\n");
+		ast_str_append(&content, 0, "<holdretrievereq>\n");
+		ast_str_append(&content, 0, "<dialogid>\n");
+		ast_str_append(&content, 0, "<callid>%s</callid>\n", conference_data->callid);
+		ast_str_append(&content, 0, "<localtag>%s</localtag>\n", conference_data->theirtag);
+		ast_str_append(&content, 0, "<remotetag>%s</remotetag>\n", conference_data->tag);
+		ast_str_append(&content, 0, "</dialogid>\n");
+		ast_str_append(&content, 0, "</holdretrievereq>\n");
+		ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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 {
+		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;
+
+		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, "<x-cisco-remotecc-response>\n");
+		ast_str_append(&content, 0, "<response>\n");
+		ast_str_append(&content, 0, "<code>%d</code>\n", res ? 500 : 200);
+		ast_str_append(&content, 0, "</response>\n");
+		ast_str_append(&content, 0, "</x-cisco-remotecc-response>\n");
+
+		add_content(&req, ast_str_buffer(content));
+		send_request(conference_data->pvt, &req, XMIT_RELIABLE, conference_data->pvt->ocseq);
+	} else {
+		if ((pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, NULL))) {
+			copy_pvt_data(pvt, conference_data->pvt);
+
+			if (res) {
+				ast_str_append(&content, 0, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+				ast_str_append(&content, 0, "<x-cisco-remotecc-request>\n");
+				ast_str_append(&content, 0, "<statuslineupdatereq>\n");
+				ast_str_append(&content, 0, "<action>notify_display</action>\n");
+				ast_str_append(&content, 0, "<dialogid>\n");
+				ast_str_append(&content, 0, "<callid>%s</callid>\n", conference_data->callid);
+				ast_str_append(&content, 0, "<localtag>%s</localtag>\n", conference_data->theirtag);
+				ast_str_append(&content, 0, "<remotetag>%s</remotetag>\n", conference_data->tag);
+				ast_str_append(&content, 0, "</dialogid>\n");
+				ast_str_append(&content, 0, "<statustext>\200S</statustext>\n");
+				ast_str_append(&content, 0, "<displaytimeout>10</displaytimeout>\n");
+				ast_str_append(&content, 0, "<linenumber>0</linenumber>\n");
+				ast_str_append(&content, 0, "<priority>1</priority>\n");
+				ast_str_append(&content, 0, "</statuslineupdatereq>\n");
+				ast_str_append(&content, 0, "</x-cisco-remotecc-request>\n");
+			} else {
+				ast_str_append(&content, 0, "<x-cisco-remotecc-request>\n");
+				ast_str_append(&content, 0, "<notifyreq>\n");
+				ast_str_append(&content, 0, "<dialogid>\n");
+				ast_str_append(&content, 0, "<callid>%s</callid>\n", conference_data->callid);
+				ast_str_append(&content, 0, "<localtag>%s</localtag>\n", conference_data->theirtag);
+				ast_str_append(&content, 0, "<remotetag>%s</remotetag>\n", conference_data->tag);
+				ast_str_append(&content, 0, "</dialogid>\n");
+				ast_str_append(&content, 0, "<feature>Join</feature>\n");
+				ast_str_append(&content, 0, "<status>Complete</status>\n");
+				ast_str_append(&content, 0, "</notifyreq>\n");
+				ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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.");
+		}
+	}
+
+	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(&notify_req, pvt, SIP_NOTIFY, NULL);
+		add_header(&notify_req, "Event", "refer");
+		snprintf(tmp, sizeof(tmp), "active;expires=%d", pvt->expiry);
+		add_header(&notify_req, "Subscription-State", tmp);
+		send_request(pvt, &notify_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 needpos = 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, NULL))) {
+		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, "<x-cisco-remotecc-request>\n");
+	ast_str_append(&content, 0, "<datapassthroughreq>\n");
+	ast_str_append(&content, 0, "<applicationid>%d</applicationid>\n", REMOTECC_CONFLIST);
+	ast_str_append(&content, 0, "<transactionid>0</transactionid>\n");
+	ast_str_append(&content, 0, "<stationsequence>StationSequenceLast</stationsequence>\n");
+	ast_str_append(&content, 0, "<displaypriority>2</displaypriority>\n");
+	ast_str_append(&content, 0, "<appinstance>0</appinstance>\n");
+	ast_str_append(&content, 0, "<routingid>0</routingid>\n");
+	ast_str_append(&content, 0, "<confid>%d</confid>\n", conference->confid);
+	ast_str_append(&content, 0, "</datapassthroughreq>\n");
+	ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+	ast_str_append(&content, 0, "<CiscoIPPhoneMenu>\n");
+	ast_str_append(&content, 0, "<Title>Conference</Title>\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, "<MenuItem>\n");
+		ast_str_append(&content, 0, "<Name>%s%s</Name>\n", status, S_OR(callerid, CALLERID_UNKNOWN));
+		ast_str_append(&content, 0, "<URL>UserCallData:%d:0:%d:0:%d</URL>\n", REMOTECC_CONFLIST, conference->confid, participant->callid);
+		ast_str_append(&content, 0, "</MenuItem>\n");
+
+		ast_channel_unlock(participant->chan);
+	}
+	ao2_unlock(conference);
+
+	ast_str_append(&content, 0, "<Prompt>Please select</Prompt>\n");
+	ast_str_append(&content, 0, "<SoftKeyItem>\n");
+	ast_str_append(&content, 0, "<Name>Exit</Name>\n");
+	ast_str_append(&content, 0, "<Position>%d</Position>\n", needpos ? 3 : 1);
+	ast_str_append(&content, 0, "<URL>SoftKey:Exit</URL>\n");
+	ast_str_append(&content, 0, "</SoftKeyItem>\n");
+	ast_str_append(&content, 0, "<SoftKeyItem>\n");
+	ast_str_append(&content, 0, "<Name>Remove</Name>\n");
+	ast_str_append(&content, 0, "<Position>%d</Position>\n", needpos ? 1 : 2);
+	ast_str_append(&content, 0, "<URL>UserCallDataSoftKey:Select:%d:0:%d:0:Remove</URL>\n", REMOTECC_CONFLIST, conference->confid);
+	ast_str_append(&content, 0, "</SoftKeyItem>\n");
+	ast_str_append(&content, 0, "<SoftKeyItem>\n");
+	ast_str_append(&content, 0, "<Name>Mute</Name>\n");
+	ast_str_append(&content, 0, "<Position>%d</Position>\n", needpos ? 2 : 3);
+	ast_str_append(&content, 0, "<URL>UserCallDataSoftKey:Select:%d:0:%d:0:Mute</URL>\n", REMOTECC_CONFLIST, conference->confid);
+	ast_str_append(&content, 0, "</SoftKeyItem>\n");
+	ast_str_append(&content, 0, "<SoftKeyItem>\n");
+	ast_str_append(&content, 0, "<Name>Update</Name>\n");
+	ast_str_append(&content, 0, "<Position>%d</Position>\n", needpos ? 4 : 4);
+	ast_str_append(&content, 0, "<URL>UserCallDataSoftKey:Update:%d:0:%d:0:Update</URL>\n", REMOTECC_CONFLIST, conference->confid);
+	ast_str_append(&content, 0, "</SoftKeyItem>\n");
+	ast_str_append(&content, 0, "</CiscoIPPhoneMenu>\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) {
+		if (event_type == PARKED_CALL_FAILED) {
+			struct sip_pvt *pvt;
+
+			if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, NULL))) {
+				return;
+			}
+			copy_pvt_data(pvt, park_data->pvt);
+
+			ast_str_append(&content, 0, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+			ast_str_append(&content, 0, "<x-cisco-remotecc-request>\n");
+			ast_str_append(&content, 0, "<statuslineupdatereq>\n");
+			ast_str_append(&content, 0, "<action>notify_display</action>\n");
+			ast_str_append(&content, 0, "<dialogid>\n");
+			ast_str_append(&content, 0, "<callid>%s</callid>\n", park_data->callid);
+			ast_str_append(&content, 0, "<localtag>%s</localtag>\n", park_data->theirtag);
+			ast_str_append(&content, 0, "<remotetag>%s</remotetag>\n", park_data->tag);
+			ast_str_append(&content, 0, "</dialogid>\n");
+			ast_str_append(&content, 0, "<statustext>\200G</statustext>\n");
+			ast_str_append(&content, 0, "<displaytimeout>10</displaytimeout>\n");
+			ast_str_append(&content, 0, "<linenumber>0</linenumber>\n");
+			ast_str_append(&content, 0, "<priority>5</priority>\n");
+			ast_str_append(&content, 0, "</statuslineupdatereq>\n");
+			ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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 {
+			struct sip_request req;
+			const char *fromdomain = S_OR(park_data->pvt->fromdomain, ast_sockaddr_stringify_host_remote(&park_data->pvt->ourip));
+			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 {
+				return;
+			}
+
+			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");
+
+			/* "parmams" is a typo in the the Cisco API, duh. */
+			ast_str_append(&content, 0, "<dialog-info xmlns=\"urn:ietf:parmams:xml:ns:dialog-info\" xmlns:call=\"urn:x-cisco:parmams:xml:ns:dialog-info:dialog:callinfo-dialog\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" version=\"%d\" state=\"full\" entity=\"%d@%s\">\n", park_data->pvt->dialogver, parkingspace, fromdomain);
+        		ast_str_append(&content, 0, "<dialog id=\"%d\">\n", parkingspace);
+			if (event_type == PARKED_CALL || event_type == PARKED_CALL_REMINDER) {
+				ast_str_append(&content, 0, "<state>confirmed</state>\n");
+			} else {
+				ast_str_append(&content, 0, "<state>terminated</state>\n");
+			}
+			ast_str_append(&content, 0, "<call:park><event>%s</event></call:park>\n", parkevent);
+			ast_str_append(&content, 0, "<local><identity display=\"\">sip:%d@%s</identity></local>\n", parkingspace, fromdomain);
+			ast_str_append(&content, 0, "<remote><identity display=\"\">sip:%d@%s</identity></remote>\n", parkingspace, fromdomain);
+			ast_str_append(&content, 0, "</dialog>\n");
+			ast_str_append(&content, 0, "</dialog-info>\n");
+
+			add_content(&req, ast_str_buffer(content));
+			send_request(park_data->pvt, &req, XMIT_RELIABLE, park_data->pvt->ocseq++);
+		}
+	} else {
+		struct sip_pvt *pvt;
+
+		if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, NULL))) {
+			return;
+		}
+		copy_pvt_data(pvt, park_data->pvt);
+
+		ast_str_append(&content, 0, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+		ast_str_append(&content, 0, "<x-cisco-remotecc-request>\n");
+		ast_str_append(&content, 0, "<statuslineupdatereq>\n");
+		ast_str_append(&content, 0, "<action>notify_display</action>\n");
+		ast_str_append(&content, 0, "<dialogid>\n");
+		ast_str_append(&content, 0, "<callid>%s</callid>\n", park_data->callid);
+		ast_str_append(&content, 0, "<localtag>%s</localtag>\n", park_data->theirtag);
+		ast_str_append(&content, 0, "<remotetag>%s</remotetag>\n", park_data->tag);
+		ast_str_append(&content, 0, "</dialogid>\n");
+		if (event_type == PARKED_CALL) {
+			ast_str_append(&content, 0, "<statustext>\200! %d</statustext>\n", parkingspace);
+		} else {
+			ast_str_append(&content, 0, "<statustext>\200^</statustext>\n");
+		}
+		ast_str_append(&content, 0, "<displaytimeout>10</displaytimeout>\n");
+		ast_str_append(&content, 0, "<linenumber>0</linenumber>\n");
+		ast_str_append(&content, 0, "<priority>5</priority>\n");
+		ast_str_append(&content, 0, "</statuslineupdatereq>\n");
+		ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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.");
+	}
+}
+
+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->uniqueid)) {
+			return;
+		} 
+
+		if (payload->event_type == PARKED_CALL) {
+			ast_softhangup(park_data->chan, AST_SOFTHANGUP_EXPLICIT);
+			ast_channel_unref(park_data->chan);
+			park_data->chan = NULL;
+		}
+
+		remotecc_park_notify(park_data, payload->event_type, payload->parkingspace, payload->timeout);
+
+		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), '?') ? "&amp;" : "?", peer->cisco_devicename);
+
+	if (!((pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, NULL)))) {
+		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, "<x-cisco-remotecc-request>\n");
+	ast_str_append(&content, 0, "<datapassthroughreq>\n");
+	ast_str_append(&content, 0, "<applicationid>0</applicationid>\n");
+	ast_str_append(&content, 0, "<transactionid>0</transactionid>\n");
+	ast_str_append(&content, 0, "<stationsequence>StationSequenceLast</stationsequence>\n");
+	ast_str_append(&content, 0, "<displaypriority>2</displaypriority>\n");
+	ast_str_append(&content, 0, "<appinstance>0</appinstance>\n");
+	ast_str_append(&content, 0, "<routingid>0</routingid>\n");
+	ast_str_append(&content, 0, "<confid>0</confid>\n");
+	ast_str_append(&content, 0, "</datapassthroughreq>\n");
+	ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, "<CiscoIPPhoneExecute>\n");
+	ast_str_append(&content, 0, "<ExecuteItem URL=\"%s\" />\n", ast_str_buffer(url));
+	ast_str_append(&content, 0, "</CiscoIPPhoneExecute>\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, NULL))) {
+			return 0;
+		}
+		copy_pvt_data(refer_pvt, pvt);
+
+		ast_str_append(&content, 0, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+		ast_str_append(&content, 0, "<x-cisco-remotecc-request>\n");
+		ast_str_append(&content, 0, "<statuslineupdatereq>\n");
+		ast_str_append(&content, 0, "<action>notify_display</action>\n");
+		ast_str_append(&content, 0, "<dialogid>\n");
+		ast_str_append(&content, 0, "<callid>%s</callid>\n", remotecc_data->dialogid.callid);
+		ast_str_append(&content, 0, "<localtag>%s</localtag>\n", remotecc_data->dialogid.localtag);
+		ast_str_append(&content, 0, "<remotetag>%s</remotetag>\n", remotecc_data->dialogid.remotetag);
+		ast_str_append(&content, 0, "</dialogid>\n");
+		ast_str_append(&content, 0, "<statustext>Quality Reporting Tool is active</statustext>\n");
+		ast_str_append(&content, 0, "<displaytimeout>5</displaytimeout>\n");
+		ast_str_append(&content, 0, "<linenumber>0</linenumber>\n");
+		ast_str_append(&content, 0, "<priority>1</priority>\n");
+		ast_str_append(&content, 0, "</statuslineupdatereq>\n");
+		ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, NULL))) {
+		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, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+	ast_str_append(&content, 0, "<x-cisco-remotecc-request>\n");
+	ast_str_append(&content, 0, "<statuslineupdatereq>\n");
+	ast_str_append(&content, 0, "<action>notify_display</action>\n");
+	ast_str_append(&content, 0, "<dialogid>\n");
+	ast_str_append(&content, 0, "<callid>%s</callid>\n", remotecc_data->dialogid.callid);
+	ast_str_append(&content, 0, "<localtag>%s</localtag>\n", remotecc_data->dialogid.localtag);
+	ast_str_append(&content, 0, "<remotetag>%s</remotetag>\n", remotecc_data->dialogid.remotetag);
+	ast_str_append(&content, 0, "</dialogid>\n");
+	ast_str_append(&content, 0, "<statustext>\200T</statustext>\n");
+	ast_str_append(&content, 0, "<displaytimeout>10</displaytimeout>\n");
+	ast_str_append(&content, 0, "<linenumber>0</linenumber>\n");
+	ast_str_append(&content, 0, "<priority>1</priority>\n");
+	ast_str_append(&content, 0, "</statuslineupdatereq>\n");
+	ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, "<x-cisco-remotecc-request>\n");
+	ast_str_append(&content, 0, "<playtonereq>\n");
+	ast_str_append(&content, 0, "<dialogid>\n");
+	ast_str_append(&content, 0, "<callid>%s</callid>\n", remotecc_data->dialogid.callid);
+	ast_str_append(&content, 0, "<localtag>%s</localtag>\n", remotecc_data->dialogid.localtag);
+	ast_str_append(&content, 0, "<remotetag>%s</remotetag>\n", remotecc_data->dialogid.remotetag);
+	ast_str_append(&content, 0, "</dialogid>\n");
+	ast_str_append(&content, 0, "<tonetype>DtZipZip</tonetype>\n");
+	ast_str_append(&content, 0, "<direction>all</direction>\n");
+	ast_str_append(&content, 0, "</playtonereq>\n");
+	ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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(char *context, 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 needpos = 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), "%H:%M %d/%m/%Y", ast_localtime(&tv, &tm, NULL));
+
+	if (!((pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, NULL)))) {
+		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, "<x-cisco-remotecc-request>\n");
+	ast_str_append(&content, 0, "<datapassthroughreq>\n");
+	ast_str_append(&content, 0, "<applicationid>0</applicationid>\n");
+	ast_str_append(&content, 0, "<transactionid>0</transactionid>\n");
+	ast_str_append(&content, 0, "<stationsequence>StationSequenceLast</stationsequence>\n");
+	ast_str_append(&content, 0, "<displaypriority>2</displaypriority>\n");
+	ast_str_append(&content, 0, "<appinstance>0</appinstance>\n");
+	ast_str_append(&content, 0, "<routingid>0</routingid>\n");
+	ast_str_append(&content, 0, "<confid>0</confid>\n");
+	ast_str_append(&content, 0, "</datapassthroughreq>\n");
+	ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, "<CiscoIPPhoneExecute>\n");
+	ast_str_append(&content, 0, "<ExecuteItem URL=\"Play:Callback.raw\" />\n");
+	ast_str_append(&content, 0, "</CiscoIPPhoneExecute>\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, NULL)))) {
+		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, "<x-cisco-remotecc-request>\n");
+	ast_str_append(&content, 0, "<datapassthroughreq>\n");
+	ast_str_append(&content, 0, "<applicationid>%d</applicationid>\n", REMOTECC_CALLBACK);
+	ast_str_append(&content, 0, "<transactionid>0</transactionid>\n");
+	ast_str_append(&content, 0, "<stationsequence>StationSequenceLast</stationsequence>\n");
+	ast_str_append(&content, 0, "<displaypriority>2</displaypriority>\n");
+	ast_str_append(&content, 0, "<appinstance>0</appinstance>\n");
+	ast_str_append(&content, 0, "<routingid>0</routingid>\n");
+	ast_str_append(&content, 0, "<confid>0</confid>\n");
+	ast_str_append(&content, 0, "</datapassthroughreq>\n");
+	ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+	ast_str_append(&content, 0, "<CiscoIPPhoneText>\n");
+	ast_str_append(&content, 0, "<Title>CallBack</Title>\n");
+	ast_str_append(&content, 0, "<Text>%s is now available at %s.\n\nPress Dial to call.\nPress Cancel to deactivate.\nPress Exit to quit this screen.</Text>\n", peer->callback->exten, date);
+	ast_str_append(&content, 0, "<Prompt>Please select</Prompt>\n");
+	ast_str_append(&content, 0, "<SoftKeyItem>\n");
+	ast_str_append(&content, 0, "<Name>Exit</Name>\n");
+	ast_str_append(&content, 0, "<Position>%d</Position>\n", needpos ? 3 : 1);
+	ast_str_append(&content, 0, "<URL>SoftKey:Exit</URL>\n");
+	ast_str_append(&content, 0, "</SoftKeyItem>\n");
+	ast_str_append(&content, 0, "<SoftKeyItem>\n");
+	ast_str_append(&content, 0, "<Name>Cancel</Name>\n");
+	ast_str_append(&content, 0, "<Position>%d</Position>\n", needpos ? 2 : 2);
+	ast_str_append(&content, 0, "<URL>UserCallData:%d:0:0:0:Cancel</URL>\n", REMOTECC_CALLBACK);
+	ast_str_append(&content, 0, "</SoftKeyItem>\n");
+	ast_str_append(&content, 0, "<SoftKeyItem>\n");
+	ast_str_append(&content, 0, "<Name>Dial</Name>\n");
+	ast_str_append(&content, 0, "<Position>%d</Position>\n", needpos ? 1 : 3);
+	ast_str_append(&content, 0, "<URL>UserCallData:%d:0:0:0:Dial</URL>\n", REMOTECC_CALLBACK);
+	ast_str_append(&content, 0, "</SoftKeyItem>\n");
+	ast_str_append(&content, 0, "</CiscoIPPhoneText>\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 needpos = 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, NULL))) {
+				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, "<x-cisco-remotecc-request>\n");
+			ast_str_append(&content, 0, "<datapassthroughreq>\n");
+			ast_str_append(&content, 0, "<applicationid>0</applicationid>\n");
+			ast_str_append(&content, 0, "<transactionid>0</transactionid>\n");
+			ast_str_append(&content, 0, "<stationsequence>StationSequenceLast</stationsequence>\n");
+			ast_str_append(&content, 0, "<displaypriority>2</displaypriority>\n");
+			ast_str_append(&content, 0, "<appinstance>0</appinstance>\n");
+			ast_str_append(&content, 0, "<routingid>0</routingid>\n");
+			ast_str_append(&content, 0, "<confid>0</confid>\n");
+			ast_str_append(&content, 0, "</datapassthroughreq>\n");
+			ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+			ast_str_append(&content, 0, "<CiscoIPPhoneText>\n");
+			ast_str_append(&content, 0, "<Title>CallBack</Title>\n");
+			ast_str_append(&content, 0, "<Text>Unable to activate callback on %s</Text>\n", S_OR(exten, CALLERID_UNKNOWN));
+			ast_str_append(&content, 0, "<Prompt>Please select</Prompt>\n");
+			ast_str_append(&content, 0, "<SoftKeyItem>\n");
+			ast_str_append(&content, 0, "<Name>Exit</Name>\n");
+			ast_str_append(&content, 0, "<Position>%d</Position>\n", needpos ? 3 : 1);
+			ast_str_append(&content, 0, "<URL>SoftKey:Exit</URL>\n");
+			ast_str_append(&content, 0, "</SoftKeyItem>\n");
+			ast_str_append(&content, 0, "</CiscoIPPhoneText>\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, NULL))) {
+			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, "<x-cisco-remotecc-request>\n");
+		ast_str_append(&content, 0, "<datapassthroughreq>\n");
+		ast_str_append(&content, 0, "<applicationid>0</applicationid>\n");
+		ast_str_append(&content, 0, "<transactionid>0</transactionid>\n");
+		ast_str_append(&content, 0, "<stationsequence>StationSequenceLast</stationsequence>\n");
+		ast_str_append(&content, 0, "<displaypriority>2</displaypriority>\n");
+		ast_str_append(&content, 0, "<appinstance>0</appinstance>\n");
+		ast_str_append(&content, 0, "<routingid>0</routingid>\n");
+		ast_str_append(&content, 0, "<confid>0</confid>\n");
+		ast_str_append(&content, 0, "</datapassthroughreq>\n");
+		ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, "<CiscoIPPhoneExecute>\n");
+		ast_str_append(&content, 0, "<ExecuteItem URL=\"Init:Services\" />\n");
+		ast_str_append(&content, 0, "</CiscoIPPhoneExecute>\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, NULL))) {
+				return 0;
+			}
+			copy_pvt_data(refer_pvt, pvt);
+
+			ast_str_append(&content, 0, "<x-cisco-remotecc-request>\n");
+			ast_str_append(&content, 0, "<initiatecallreq>\n");
+			ast_str_append(&content, 0, "<dialstring>%s</dialstring>\n", peer->callback->exten);
+			ast_str_append(&content, 0, "<linenumber>%d</linenumber>\n", peer->cisco_lineindex);
+			ast_str_append(&content, 0, "<globalcallid></globalcallid>\n");
+			ast_str_append(&content, 0, "</initiatecallreq>\n");
+			ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, NULL))) {
+		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, "<x-cisco-remotecc-request>\n");
+	ast_str_append(&content, 0, "<datapassthroughreq>\n");
+	ast_str_append(&content, 0, "<applicationid>%d</applicationid>\n", REMOTECC_CALLBACK);
+	ast_str_append(&content, 0, "<transactionid>0</transactionid>\n");
+	ast_str_append(&content, 0, "<stationsequence>StationSequenceLast</stationsequence>\n");
+	ast_str_append(&content, 0, "<displaypriority>2</displaypriority>\n");
+	ast_str_append(&content, 0, "<appinstance>0</appinstance>\n");
+	ast_str_append(&content, 0, "<routingid>0</routingid>\n");
+	ast_str_append(&content, 0, "<confid>0</confid>\n");
+	ast_str_append(&content, 0, "</datapassthroughreq>\n");
+	ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+	ast_str_append(&content, 0, "<CiscoIPPhoneText>\n");
+	ast_str_append(&content, 0, "<Title>CallBack</Title>\n");
+
+	if (peer->callback) {
+		ast_str_append(&content, 0, "<Text>CallBack is activated on %s.\n\nPress Cancel to deactivate.\nPress Exit to quit this screen.</Text>", peer->callback->exten);
+	} else {
+		ast_str_append(&content, 0, "<Text>CallBack is not activated.</Text>");
+	}
+
+	ast_str_append(&content, 0, "<Prompt>Please select</Prompt>\n");
+	ast_str_append(&content, 0, "<SoftKeyItem>\n");
+	ast_str_append(&content, 0, "<Name>Exit</Name>\n");
+	ast_str_append(&content, 0, "<Position>%d</Position>\n", needpos ? 3 : 1);
+	ast_str_append(&content, 0, "<URL>SoftKey:Exit</URL>\n");
+	ast_str_append(&content, 0, "</SoftKeyItem>\n");
+
+	if (peer->callback) {
+		ast_str_append(&content, 0, "<SoftKeyItem>\n");
+		ast_str_append(&content, 0, "<Name>Cancel</Name>\n");
+		ast_str_append(&content, 0, "<Position>%d</Position>\n", needpos ? 2 : 2);
+		ast_str_append(&content, 0, "<URL>UserCallData:%d:0:0:0:Cancel</URL>\n", REMOTECC_CALLBACK);
+		ast_str_append(&content, 0, "</SoftKeyItem>\n");
+	}
+
+	ast_str_append(&content, 0, "</CiscoIPPhoneText>\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 Returns the pointer to the provided tag buffer,
@@ -25522,16 +29101,130 @@
 	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;
+	char to[256];
+	char *uri, *name, *domain;
+	const char *content_type = sip_get_header(req, "Content-Type");
+	char *dialog_body;
+	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 *state_text;
+	int offhook = 0;
+
+	ast_copy_string(to, sip_get_header(req, "To"), sizeof(to));
+	uri = get_in_brackets(to);
+	uri = remove_uri_parameters(uri);
+
+	if (parse_uri_legacy_check(uri, "sip:,sips", &name, NULL, &domain, NULL)) {
+		return -1;
+	}
+	SIP_PEDANTIC_DECODE(name);
+
+	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;
+	}
+
+	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);
+
+	if (!strcasecmp(state_text, "trying")) {
+		offhook = 1;
+	} else if (!strcasecmp(state_text, "terminated")) {
+		offhook = -1;
+	} else {
+		ast_log(LOG_WARNING, "Invalid content in state node %s\n", state_text);
+		ast_xml_free_text(state_text);
+		ast_xml_close(dialog_doc);
+		return -1;
+	}
+
+	ast_xml_free_text(state_text);
+	ast_xml_close(dialog_doc);
+
+	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';
 	}
 
@@ -25546,6 +29239,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
 
@@ -25554,7 +29248,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);
@@ -25562,7 +29262,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);
@@ -25676,6 +29376,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);
@@ -27115,7 +30820,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;
@@ -27124,6 +30829,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"), "<urn:x-cisco-remotecc:token-registration>")) {
+		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",
@@ -27600,6 +31345,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);
@@ -27970,7 +31719,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;
 	}
@@ -28090,6 +31839,100 @@
 	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");
+			}
+		}
+
+		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)
@@ -28366,6 +32209,178 @@
 	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");
+			}
+		}
+
+		*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);
+				}
+			}
+		}
+
+		*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)
 {
@@ -28374,6 +32389,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 */
@@ -28507,12 +32523,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 {
@@ -28635,6 +32658,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 */
@@ -28679,6 +32729,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 {
@@ -28701,10 +32754,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, "<?xml version=\"1.0\" ?>\n");
+				if (feature == FE_DONOTDISTURB) {
+					add_content(&resp, "<SetDoNotDisturbResponse xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\" />\n");
+				} else if (feature == FE_CALLFORWARD) {
+					add_content(&resp, "<SetForwardingResponse xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\" />\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;
@@ -28725,6 +32806,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) {
@@ -28739,11 +32823,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 */
@@ -29090,7 +33170,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);
@@ -29550,7 +33630,7 @@
  *  are processed.  The peer will still be valid however.
  *  \returns -1 on failure, 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;
@@ -29580,7 +33660,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);
@@ -29598,7 +33678,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);
@@ -29648,13 +33728,356 @@
 	/* 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;
+
+		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, NULL))) {
+			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, "<x-cisco-remotecc-request>\n");
+		ast_str_append(&content, 0, "<dndupdate>\n");
+		ast_str_append(&content, 0, "<state>%s</state>\n", peer->donotdisturb ? "enable" : "disable");
+		ast_str_append(&content, 0, "<option>%s</option>\n", ast_test_flag(&peer->flags[2], SIP_PAGE3_DND_BUSY) ? "callreject" : "ringeroff");
+		ast_str_append(&content, 0, "</dndupdate>\n");
+		ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, "<x-cisco-remotecc-request>\n");
+		ast_str_append(&content, 0, "<hlogupdate>\n");
+		ast_str_append(&content, 0, "<status>%s</status>\n", peer->huntgroup ? "on" : "off");
+		ast_str_append(&content, 0, "</hlogupdate>\n");
+		ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, "<x-cisco-remotecc-request>\n");
+		ast_str_append(&content, 9, "<bulkupdate>\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, "<contact line=\"%d\">\n", peer->cisco_lineindex);
+		ast_str_append(&content, 0, "<mwi>%s</mwi>\n", newmsgs ? "yes" : "no");
+		ast_str_append(&content, 0, "<emwi>\n");
+		ast_str_append(&content, 0, "<voice-msg new=\"%d\" old=\"%d\" />\n", newmsgs, oldmsgs);
+		ast_str_append(&content, 0, "</emwi>\n");
+		ast_str_append(&content, 0, "<cfwdallupdate>\n");
+		ast_str_append(&content, 0, "<fwdaddress>%s</fwdaddress>\n", peer->callforward);
+		ast_str_append(&content, 0, "<tovoicemail>%s</tovoicemail>\n", !ast_strlen_zero(peer->vmexten) && !strcmp(peer->callforward, peer->vmexten) ? "on" : "off");
+		ast_str_append(&content, 0, "</cfwdallupdate>\n");
+		ast_str_append(&content, 0, "</contact>\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, "<contact line=\"%d\">\n", alias->peer->cisco_lineindex);
+			ast_str_append(&content, 0, "<mwi>%s</mwi>\n", newmsgs ? "yes" : "no");
+			ast_str_append(&content, 0, "<emwi>\n");
+			ast_str_append(&content, 0, "<voice-msg new=\"%d\" old=\"%d\" />\n", newmsgs, oldmsgs);
+			ast_str_append(&content, 0, "</emwi>\n");
+			ast_str_append(&content, 0, "<cfwdallupdate>\n");
+			ast_str_append(&content, 0, "<fwdaddress>%s</fwdaddress>\n", alias->peer->callforward);
+			ast_str_append(&content, 0, "<tovoicemail>%s</tovoicemail>\n", !ast_strlen_zero(alias->peer->vmexten) && !strcmp(alias->peer->callforward, alias->peer->vmexten) ? "on" : "off");
+			ast_str_append(&content, 0, "</cfwdallupdate>\n");
+			ast_str_append(&content, 0, "</contact>\n");
+		}
+
+		ast_str_append(&content, 0, "</bulkupdate>\n");
+		ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, "<?xml version=\"1.0\"?>\n");
+		add_content(&req, "<DoNotDisturbEvent xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">\n");
+		snprintf(tmp, sizeof(tmp), "<device><notKnown /></device>\n<doNotDisturbOn>%s</doNotDisturbOn>\n", peer->donotdisturb ? "true" : "false");
+		add_content(&req, tmp);
+		add_content(&req, "</DoNotDisturbEvent>\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, "<?xml version=\"1.0\"?>\n");
+		add_content(&req, "<ForwardingEvent xmlns=\"http://www.ecma-international.org/standards/ecma-323/csta/ed3\">\n");
+		add_content(&req, "<device><notKnown /></device>\n<forwardingType>forwardImmediate</forwardingType>\n");
+		snprintf(tmp, sizeof(tmp), "<forwardStatus>%s</forwardStatus>\n<forwardTo>%s</forwardTo>\n", !ast_strlen_zero(peer->callforward) ? "true" : "false", peer->callforward);
+		add_content(&req, tmp);
+		add_content(&req, "</ForwardingEvent>\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);
@@ -30357,6 +34780,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,
@@ -30510,13 +34952,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);
 
@@ -30554,7 +34996,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)))
@@ -30572,6 +35014,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.
  *
@@ -31052,9 +35523,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);
@@ -31320,7 +35803,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;
@@ -31328,6 +35810,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) */
@@ -31379,7 +35863,6 @@
 
 	while ((mbox = strsep(&next, ","))) {
 		struct sip_mailbox *mailbox;
-		int duplicate = 0;
 
 		/* remove leading/trailing whitespace from mailbox string */
 		mbox = ast_strip(mbox);
@@ -31390,12 +35873,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;
 		}
 
@@ -31411,6 +35893,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)
 {
@@ -31434,6 +35997,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 */
@@ -31533,9 +36097,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 */
@@ -31739,7 +36312,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) {
@@ -31909,6 +36482,40 @@
 				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 {
 				ast_rtp_dtls_cfg_parse(&peer->dtls_cfg, v->name, v->value);
 			}
@@ -31983,6 +36590,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);
@@ -31990,6 +36600,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)) {
@@ -32150,6 +36774,22 @@
 		reg_source_db(peer);
 	}
 
+	if (!peer->is_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) &&
@@ -32158,7 +36798,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;
@@ -32166,9 +36811,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(&reg_string, "%s?%s:%s@%s/%s", peer->name, peer->username, !ast_strlen_zero(peer->remotesecret) ? peer->remotesecret : peer->secret, peer->tohost, peer->callback) >= 0) {
+		if (ast_asprintf(&reg_string, "%s?%s:%s@%s/%s", peer->name, peer->username, !ast_strlen_zero(peer->remotesecret) ? peer->remotesecret : peer->secret, peer->tohost, peer->callbackexten) >= 0) {
 			sip_register(reg_string, 0); /* XXX TODO: count in registry_count */
 			ast_free(reg_string);
 		}
@@ -33760,6 +38405,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
@@ -33891,6 +38537,425 @@
 	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, NULL))) {
+			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, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+			ast_str_append(&content, 0, "<x-cisco-remotecc-request>\n");
+			ast_str_append(&content, 0, "<statuslineupdatereq>\n");
+			ast_str_append(&content, 0, "<action>notify_display</action>\n");
+			ast_str_append(&content, 0, "<statustext>%s</statustext>\n", display);
+			ast_str_append(&content, 0, "<displaytimeout>10</displaytimeout>\n");
+			ast_str_append(&content, 0, "<linenumber>0</linenumber>\n");
+			ast_str_append(&content, 0, "<priority>1</priority>\n");
+			ast_str_append(&content, 0, "</statuslineupdatereq>\n");
+			ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, "<x-cisco-remotecc-request>\n");
+			ast_str_append(&content, 0, "<playtonereq>\n");
+			ast_str_append(&content, 0, "<tonetype>DtZipZip</tonetype>\n");
+			ast_str_append(&content, 0, "<direction>all</direction>\n");
+			ast_str_append(&content, 0, "</playtonereq>\n");
+			ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, "<x-cisco-remotecc-request>\n");
+		ast_str_append(&content, 0, "<datapassthroughreq>\n");
+		ast_str_append(&content, 0, "<applicationid>0</applicationid>\n");
+		ast_str_append(&content, 0, "<transactionid>0</transactionid>\n");
+		ast_str_append(&content, 0, "<stationsequence>StationSequenceLast</stationsequence>\n");
+		ast_str_append(&content, 0, "<displaypriority>2</displaypriority>\n");
+		ast_str_append(&content, 0, "<appinstance>0</appinstance>\n");
+		ast_str_append(&content, 0, "<routingid>0</routingid>\n");
+		ast_str_append(&content, 0, "<confid>0</confid>\n");
+		ast_str_append(&content, 0, "</datapassthroughreq>\n");
+		ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, "<startMedia>\n");
+		if (volume != -1) {
+			ast_str_append(&content, 0, "<mediaStream receiveVolume=\"%d\">\n", volume);
+		} else {
+			ast_str_append(&content, 0, "<mediaStream>\n");
+		}
+		ast_str_append(&content, 0, "<type>audio</type>\n");
+		ast_str_append(&content, 0, "<codec>%s</codec>\n", codec);
+		ast_str_append(&content, 0, "<mode>receive</mode>\n");
+		ast_str_append(&content, 0, "<address>%s</address>\n", ast_sockaddr_stringify_fmt(&ouraddr, AST_SOCKADDR_STR_ADDR));
+		ast_str_append(&content, 0, "<port>%d</port>\n", port);
+		ast_str_append(&content, 0, "</mediaStream>\n");
+		ast_str_append(&content, 0, "</startMedia>\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, NULL))) {
+			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, "<x-cisco-remotecc-request>\n");
+			ast_str_append(&content, 0, "<datapassthroughreq>\n");
+			ast_str_append(&content, 0, "<applicationid>0</applicationid>\n");
+			ast_str_append(&content, 0, "<transactionid>0</transactionid>\n");
+			ast_str_append(&content, 0, "<stationsequence>StationSequenceLast</stationsequence>\n");
+			ast_str_append(&content, 0, "<displaypriority>2</displaypriority>\n");
+			ast_str_append(&content, 0, "<appinstance>0</appinstance>\n");
+			ast_str_append(&content, 0, "<routingid>0</routingid>\n");
+			ast_str_append(&content, 0, "<confid>0</confid>\n");
+			ast_str_append(&content, 0, "</datapassthroughreq>\n");
+			ast_str_append(&content, 0, "</x-cisco-remotecc-request>\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, "<stopMedia>\n");
+			ast_str_append(&content, 0, "<mediaStream />\n");
+			ast_str_append(&content, 0, "</stopMedia>\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)
@@ -33996,6 +39061,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) {
@@ -34158,6 +39228,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();
 
@@ -34328,9 +39401,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;
 	}
@@ -34492,6 +39565,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"),
@@ -35329,6 +40405,9 @@
 	MEMBER(sip_peer, ringing, AST_DATA_INTEGER)		\
 	MEMBER(sip_peer, onhold, AST_DATA_INTEGER)		\
 	MEMBER(sip_peer, call_limit, AST_DATA_INTEGER)		\
+	MEMBER(sip_peer, donotdisturb, AST_DATA_INTEGER)        \
+	MEMBER(sip_peer, huntgroup, AST_DATA_INTEGER)           \
+	MEMBER(sip_peer, callforward, AST_DATA_STRING)          \
 	MEMBER(sip_peer, t38_maxdatagram, AST_DATA_INTEGER)	\
 	MEMBER(sip_peer, maxcallbitrate, AST_DATA_INTEGER)	\
 	MEMBER(sip_peer, rtptimeout, AST_DATA_SECONDS)		\
@@ -35607,6 +40686,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
@@ -35680,6 +40760,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);
@@ -35706,6 +40787,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-13.38.2.orig/channels/sip/config_parser.c asterisk-13.38.2/channels/sip/config_parser.c
--- asterisk-13.38.2.orig/channels/sip/config_parser.c	2021-04-22 22:53:09.786424920 +1200
+++ asterisk-13.38.2/channels/sip/config_parser.c	2021-04-22 22:54:57.431908049 +1200
@@ -256,7 +256,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, ""), "\"", "\""));
@@ -311,7 +311,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")     ||
@@ -340,7 +340,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")     ||
@@ -369,7 +369,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")     ||
@@ -398,7 +398,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")     ||
@@ -427,7 +427,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")     ||
@@ -456,7 +456,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")     ||
@@ -485,7 +485,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")     ||
@@ -514,7 +514,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")     ||
@@ -584,7 +584,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")     ||
@@ -613,7 +613,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-13.38.2.orig/channels/sip/include/sip.h asterisk-13.38.2/channels/sip/include/sip.h
--- asterisk-13.38.2.orig/channels/sip/include/sip.h	2021-04-22 22:53:09.786424920 +1200
+++ asterisk-13.38.2/channels/sip/include/sip.h	2021-04-22 22:54:57.435907954 +1200
@@ -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) */
@@ -347,7 +348,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 */
@@ -368,7 +369,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)
@@ -385,11 +386,28 @@
 #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_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
 
@@ -471,6 +489,9 @@
 	PIDF_XML,
 	MWI_NOTIFICATION,
 	CALL_COMPLETION,
+	PRESENCE,
+	FEATURE_EVENTS,
+	REMOTECC_XML,
 };
 
 /*! \brief The number of media types in enum \ref media_type below. */
@@ -927,10 +948,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 */
@@ -1046,6 +1071,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 */
@@ -1171,6 +1200,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) */
@@ -1204,6 +1236,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;
 };
@@ -1253,6 +1286,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 */
@@ -1288,7 +1345,13 @@
 		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_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.
@@ -1311,6 +1374,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 */
@@ -1323,6 +1389,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 */
@@ -1351,11 +1426,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 */
@@ -1394,7 +1474,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 */
 	);
@@ -1435,6 +1515,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-13.38.2.orig/configs/samples/res_parking.conf.sample asterisk-13.38.2/configs/samples/res_parking.conf.sample
--- asterisk-13.38.2.orig/configs/samples/res_parking.conf.sample	2021-04-22 22:53:09.790424827 +1200
+++ asterisk-13.38.2/configs/samples/res_parking.conf.sample	2021-04-22 22:54:57.435907954 +1200
@@ -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-13.38.2.orig/configs/samples/sip.conf.sample asterisk-13.38.2/configs/samples/sip.conf.sample
--- asterisk-13.38.2.orig/configs/samples/sip.conf.sample	2021-04-22 22:53:09.790424827 +1200
+++ asterisk-13.38.2/configs/samples/sip.conf.sample	2021-04-22 22:54:57.435907954 +1200
@@ -1573,6 +1573,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-13.38.2.orig/configs/samples/sip_notify.conf.sample asterisk-13.38.2/configs/samples/sip_notify.conf.sample
--- asterisk-13.38.2.orig/configs/samples/sip_notify.conf.sample	2021-04-22 22:53:09.790424827 +1200
+++ asterisk-13.38.2/configs/samples/sip_notify.conf.sample	2021-04-22 22:54:57.439907859 +1200
@@ -55,3 +55,26 @@
 
 [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}
+
+[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}
+
diff -durN asterisk-13.38.2.orig/include/asterisk/channel.h asterisk-13.38.2/include/asterisk/channel.h
--- asterisk-13.38.2.orig/include/asterisk/channel.h	2021-04-22 22:53:09.822424092 +1200
+++ asterisk-13.38.2/include/asterisk/channel.h	2021-04-22 22:54:57.439907859 +1200
@@ -637,6 +637,8 @@
 
 	int (* const devicestate)(const char *device_number);	/*!< Devicestate call back */
 
+	int (* const presencestate)(const char *presence_number, char **subtype, char **message);   /*!< Presencestate call back */
+
 	/*!
 	 * \brief Start sending a literal DTMF digit
 	 *
diff -durN asterisk-13.38.2.orig/include/asterisk/parking.h asterisk-13.38.2/include/asterisk/parking.h
--- asterisk-13.38.2.orig/include/asterisk/parking.h	2021-04-22 22:53:09.822424092 +1200
+++ asterisk-13.38.2/include/asterisk/parking.h	2021-04-22 22:54:57.439907859 +1200
@@ -50,6 +50,7 @@
 	PARKED_CALL_UNPARKED,
 	PARKED_CALL_FAILED,
 	PARKED_CALL_SWAP,
+	PARKED_CALL_REMINDER,
 };
 
 /*!
diff -durN asterisk-13.38.2.orig/main/cel.c asterisk-13.38.2/main/cel.c
--- asterisk-13.38.2.orig/main/cel.c	2021-04-22 22:53:09.826424000 +1200
+++ asterisk-13.38.2/main/cel.c	2021-04-22 22:54:57.443907764 +1200
@@ -1150,6 +1150,8 @@
 	case PARKED_CALL_SWAP:
 		reason = "ParkedCallSwap";
 		break;
+	case PARKED_CALL_REMINDER:
+		return;
 	}
 
 	if (parked_payload->retriever) {
diff -durN asterisk-13.38.2.orig/main/channel.c asterisk-13.38.2/main/channel.c
--- asterisk-13.38.2.orig/main/channel.c	2021-04-22 22:53:09.826424000 +1200
+++ asterisk-13.38.2/main/channel.c	2021-04-22 22:54:57.443907764 +1200
@@ -60,6 +60,7 @@
 #include "asterisk/app.h"
 #include "asterisk/transcap.h"
 #include "asterisk/devicestate.h"
+#include "asterisk/presencestate.h"
 #include "asterisk/threadstorage.h"
 #include "asterisk/slinfactory.h"
 #include "asterisk/audiohook.h"
@@ -273,7 +274,7 @@
 /*! \brief Show channel types - CLI command */
 static char *handle_cli_core_show_channeltypes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-#define FORMAT  "%-15.15s  %-40.40s %-12.12s %-12.12s %-12.12s\n"
+#define FORMAT  "%-15.15s  %-40.40s %-13.13s %-13.13s %-13.13s %-13.13s\n"
 	struct chanlist *cl;
 	int count_chan = 0;
 
@@ -292,13 +293,13 @@
 	if (a->argc != 3)
 		return CLI_SHOWUSAGE;
 
-	ast_cli(a->fd, FORMAT, "Type", "Description",       "Devicestate", "Indications", "Transfer");
-	ast_cli(a->fd, FORMAT, "-----------", "-----------", "-----------", "-----------", "-----------");
+	ast_cli(a->fd, FORMAT, "Type", "Description", "Devicestate", "Presencestate", "Indications", "Transfer");
 
 	AST_RWLIST_RDLOCK(&backends);
 	AST_RWLIST_TRAVERSE(&backends, cl, list) {
 		ast_cli(a->fd, FORMAT, cl->tech->type, cl->tech->description,
 			(cl->tech->devicestate) ? "yes" : "no",
+			(cl->tech->presencestate) ? "yes" : "no",
 			(cl->tech->indicate) ? "yes" : "no",
 			(cl->tech->transfer) ? "yes" : "no");
 		count_chan++;
@@ -371,6 +372,7 @@
 	ast_cli(a->fd,
 		"-- Info about channel driver: %s --\n"
 		"  Device State: %s\n"
+		"Presence State: %s\n"
 		"    Indication: %s\n"
 		"     Transfer : %s\n"
 		"  Capabilities: %s\n"
@@ -381,6 +383,7 @@
 		"  Text Support: %s\n",
 		cl->tech->type,
 		(cl->tech->devicestate) ? "yes" : "no",
+		(cl->tech->presencestate) ? "yes" : "no",
 		(cl->tech->indicate) ? "yes" : "no",
 		(cl->tech->transfer) ? "yes" : "no",
 		ast_format_cap_get_names(cl->tech->capabilities, &codec_buf),
@@ -7944,6 +7947,7 @@
 		ast_data_add_str(data_type, "name", cl->tech->type);
 		ast_data_add_str(data_type, "description", cl->tech->description);
 		ast_data_add_bool(data_type, "devicestate", cl->tech->devicestate ? 1 : 0);
+		ast_data_add_bool(data_type, "presencestate", cl->tech->presencestate ? 1 : 0);
 		ast_data_add_bool(data_type, "indications", cl->tech->indicate ? 1 : 0);
 		ast_data_add_bool(data_type, "transfer", cl->tech->transfer ? 1 : 0);
 		ast_data_add_bool(data_type, "send_digit_begin", cl->tech->send_digit_begin ? 1 : 0);
diff -durN asterisk-13.38.2.orig/main/pbx.c asterisk-13.38.2/main/pbx.c
--- asterisk-13.38.2.orig/main/pbx.c	2021-04-22 22:53:09.830423909 +1200
+++ asterisk-13.38.2/main/pbx.c	2021-04-22 22:54:57.447907669 +1200
@@ -5190,7 +5190,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)) {
@@ -5217,8 +5217,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-13.38.2.orig/main/presencestate.c asterisk-13.38.2/main/presencestate.c
--- asterisk-13.38.2.orig/main/presencestate.c	2021-04-22 22:53:09.830423909 +1200
+++ asterisk-13.38.2/main/presencestate.c	2021-04-22 22:54:57.447907669 +1200
@@ -73,13 +73,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);
@@ -147,6 +147,7 @@
 static enum ast_presence_state ast_presence_state_helper(const char *presence_provider, char **subtype, char **message, int check_cache)
 {
 	struct presence_state_provider *provider;
+	const struct ast_channel_tech *chan_tech;
 	char *address;
 	char *label = ast_strdupa(presence_provider);
 	int res = AST_PRESENCE_INVALID;
@@ -161,6 +162,17 @@
 		}
 	}
 
+	if ((address = strchr(label, '/'))) {
+		*address = '\0';
+		address++;
+
+		if ((chan_tech = ast_get_channel_tech(label)) && chan_tech->presencestate) {
+			res = chan_tech->presencestate(address, subtype, message);
+		}
+
+		return res;
+	}
+
 	if ((address = strchr(label, ':'))) {
 		*address = '\0';
 		address++;
diff -durN asterisk-13.38.2.orig/res/parking/parking_applications.c asterisk-13.38.2/res/parking/parking_applications.c
--- asterisk-13.38.2.orig/res/parking/parking_applications.c	2021-04-22 22:53:09.838423725 +1200
+++ asterisk-13.38.2/res/parking/parking_applications.c	2021-04-22 22:54:57.451907574 +1200
@@ -79,6 +79,11 @@
 						<para>Use a timeout of <literal>duration</literal> seconds instead
 							of the timeout specified by the parking lot.</para>
 					</option>
+					<option name="T">
+						<argument name="duration" required="true" />
+						<para>User a delay of <literal>duration</literal> seconds before
+							sending a reminder warning to subscribers.</para>
+					</option>
 				</optionlist>
 			</parameter>
 		</syntax>
@@ -243,6 +248,7 @@
 enum park_args {
 	OPT_ARG_COMEBACK,
 	OPT_ARG_TIMEOUT,
+	OPT_ARG_REMINDER,
 	OPT_ARG_ARRAY_SIZE /* Always the last element of the enum */
 };
 
@@ -252,6 +258,7 @@
 	MUXFLAG_NOANNOUNCE = (1 << 2),
 	MUXFLAG_COMEBACK_OVERRIDE = (1 << 3),
 	MUXFLAG_TIMEOUT_OVERRIDE = (1 << 4),
+	MUXFLAG_REMINDER_OVERRIDE = (1 << 5),
 };
 
 AST_APP_OPTIONS(park_opts, {
@@ -260,6 +267,7 @@
 	AST_APP_OPTION('s', MUXFLAG_NOANNOUNCE),
 	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('T', MUXFLAG_REMINDER_OVERRIDE, OPT_ARG_REMINDER),
 });
 
 static int apply_option_timeout (int *var, char *timeout_arg)
@@ -277,7 +285,22 @@
 	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)
+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, int *reminder_delay, char **comeback_override, char **lot_name)
 {
 	char *parse;
 	struct ast_flags flags = { 0 };
@@ -300,6 +323,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]);
 		}
@@ -362,7 +391,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;
@@ -414,6 +443,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) {
@@ -462,6 +492,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) {
@@ -485,7 +516,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)
 {
 	struct ast_bridge *parking_bridge;
 	RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
@@ -521,7 +552,7 @@
 	/* Apply relevant bridge roles and such to the parking channel */
 	parking_channel_set_roles(parkee, lot, use_ringing);
 	setup_park_common_datastore(parkee, ast_channel_uniqueid(parker), comeback_override, randomize, time_limit,
-		silence_announcements);
+		reminder_delay, silence_announcements);
 	return parking_bridge;
 }
 
@@ -531,16 +562,17 @@
 	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);
 
 	if (app_data) {
-		park_app_parse_data(app_data, silence_announcements, &use_ringing, &randomize, &time_limit, &comeback_override, &lot_name_app_arg);
+		park_app_parse_data(app_data, silence_announcements, &use_ringing, &randomize, &time_limit, &reminder_delay, &comeback_override, &lot_name_app_arg);
 	}
 
 	return park_common_setup(parkee, parker, lot_name_app_arg, comeback_override, use_ringing,
-		randomize, time_limit, silence_announcements ? *silence_announcements : 0);
+		randomize, time_limit, reminder_delay, silence_announcements ? *silence_announcements : 0);
 
 }
 
diff -durN asterisk-13.38.2.orig/res/parking/parking_bridge.c asterisk-13.38.2/res/parking/parking_bridge.c
--- asterisk-13.38.2.orig/res/parking/parking_bridge.c	2021-04-22 22:53:09.838423725 +1200
+++ asterisk-13.38.2/res/parking/parking_bridge.c	2021-04-22 22:54:57.451907574 +1200
@@ -106,7 +106,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 */
@@ -163,6 +163,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);
@@ -249,6 +250,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",
@@ -288,7 +290,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);
@@ -313,6 +315,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;
diff -durN asterisk-13.38.2.orig/res/parking/parking_bridge_features.c asterisk-13.38.2/res/parking/parking_bridge_features.c
--- asterisk-13.38.2.orig/res/parking/parking_bridge_features.c	2021-04-22 22:53:09.838423725 +1200
+++ asterisk-13.38.2/res/parking/parking_bridge_features.c	2021-04-22 22:54:57.451907574 +1200
@@ -663,6 +663,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;
@@ -711,6 +720,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);
+	}
+}
 
 struct ast_parking_bridge_feature_fn_table parking_provider = {
 	.module_version = PARKING_MODULE_VERSION,
diff -durN asterisk-13.38.2.orig/res/parking/parking_manager.c asterisk-13.38.2/res/parking/parking_manager.c
--- asterisk-13.38.2.orig/res/parking/parking_manager.c	2021-04-22 22:53:09.838423725 +1200
+++ asterisk-13.38.2/res/parking/parking_manager.c	2021-04-22 22:54:57.455907479 +1200
@@ -455,7 +455,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");
@@ -659,6 +659,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 exclusivelly 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-13.38.2.orig/res/parking/parking_ui.c asterisk-13.38.2/res/parking/parking_ui.c
--- asterisk-13.38.2.orig/res/parking/parking_ui.c	2021-04-22 22:53:09.838423725 +1200
+++ asterisk-13.38.2/res/parking/parking_ui.c	2021-04-22 22:54:57.455907479 +1200
@@ -60,6 +60,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-13.38.2.orig/res/parking/res_parking.h asterisk-13.38.2/res/parking/res_parking.h
--- asterisk-13.38.2.orig/res/parking/res_parking.h	2021-04-22 22:53:09.838423725 +1200
+++ asterisk-13.38.2/res/parking/res_parking.h	2021-04-22 22:54:57.455907479 +1200
@@ -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 */
 };
@@ -255,6 +257,16 @@
 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
  *
@@ -409,7 +421,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
@@ -438,6 +450,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-13.38.2.orig/res/res_format_attr_h264.c asterisk-13.38.2/res/res_format_attr_h264.c
--- asterisk-13.38.2.orig/res/res_format_attr_h264.c	2021-04-22 22:53:09.838423725 +1200
+++ asterisk-13.38.2/res/res_format_attr_h264.c	2021-04-22 22:54:57.455907479 +1200
@@ -49,6 +49,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;
@@ -73,6 +74,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)
@@ -162,6 +164,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;
 }
 
@@ -309,6 +317,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,
@@ -316,6 +360,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-13.38.2.orig/res/res_parking.c asterisk-13.38.2/res/res_parking.c
--- asterisk-13.38.2.orig/res/res_parking.c	2021-04-22 22:53:09.838423725 +1200
+++ asterisk-13.38.2/res/res_parking.c	2021-04-22 22:54:57.459907384 +1200
@@ -113,6 +113,9 @@
 				<configOption name="parkingtime" default="45">
 					<synopsis>Amount of time a call will remain parked before giving up (in seconds).</synopsis>
 				</configOption>
+				<configOption name="remindertime" default="0">
+					<synopsis>Amount of time before sending a reminder warning (in seconds).</synopsis>
+				</configOption>
 				<configOption name="parkedmusicclass">
 					<synopsis>Which music class to use for parked calls. They will use the default if unspecified.</synopsis>
 				</configOption>
@@ -958,6 +961,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;
@@ -1235,6 +1239,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));
