From 77002bc377f19ea11e60732c486b6ef371688773 Mon Sep 17 00:00:00 2001
From: Joshua Colp <jcolp@digium.com>
Date: Sat, 22 Jun 2013 14:03:22 +0000
Subject: [PATCH] Merge in current pimp_my_sip work, including:

1. Security events
2. Websocket support
3. Diversion header + redirecting support
4. An anonymous endpoint identifier
5. Inbound extension state subscription support
6. PIDF notify generation
7. One touch recording support (special thanks Sean Bright!)
8. Blind and attended transfer support
9. Automatic inbound registration expiration
10. SRTP support
11. Media offer control dialplan function
12. Connected line support
13. SendText() support
14. Qualify support
15. Inband DTMF detection
16. Call and pickup groups
17. Messaging support

Thanks everyone!

Side note: I'm reminded of the song "How Far We've Come" by Matchbox Twenty.


git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@392565 65c4cc65-6c06-0410-ace0-fbb531ad65f3
---
 channels/chan_gulp.c                         | 461 +++++++++-
 channels/chan_sip.c                          | 136 +--
 channels/sip/include/sdp_crypto.h            |  85 --
 channels/sip/include/sip.h                   |   6 +-
 channels/sip/include/srtp.h                  |  59 --
 channels/sip/srtp.c                          |  55 --
 configs/res_sip.conf.sample                  |   2 +
 include/asterisk/res_sip.h                   | 178 +++-
 include/asterisk/res_sip_exten_state.h       |  94 ++
 include/asterisk/res_sip_pubsub.h            |  17 +-
 include/asterisk/res_sip_session.h           |  42 +
 include/asterisk/sdp_srtp.h                  | 125 +++
 main/pbx.c                                   |   2 +-
 channels/sip/sdp_crypto.c => main/sdp_srtp.c | 130 ++-
 res/res_sip.c                                | 204 ++++-
 res/res_sip.exports.in                       |  10 +
 res/res_sip/config_transport.c               |   7 +-
 res/res_sip/include/res_sip_private.h        |   8 +
 res/res_sip/location.c                       |  66 ++
 res/res_sip/security_events.c                | 234 +++++
 res/res_sip/sip_configuration.c              | 181 +++-
 res/res_sip/sip_distributor.c                |  14 +
 res/res_sip/sip_options.c                    | 791 ++++++++++++-----
 res/res_sip_caller_id.c                      |   2 +-
 res/res_sip_diversion.c                      | 346 ++++++++
 res/res_sip_dtmf_info.c                      |   3 +-
 res/res_sip_endpoint_identifier_anonymous.c  | 125 +++
 res/res_sip_exten_state.c                    | 620 +++++++++++++
 res/res_sip_exten_state.exports.in           |   7 +
 res/res_sip_messaging.c                      | 660 ++++++++++++++
 res/res_sip_one_touch_record_info.c          | 118 +++
 res/res_sip_outbound_registration.c          |   4 +-
 res/res_sip_pidf.c                           | 341 ++++++++
 res/res_sip_pubsub.c                         |  55 +-
 res/res_sip_pubsub.exports.in                |   1 +
 res/res_sip_refer.c                          | 860 +++++++++++++++++++
 res/res_sip_registrar.c                      |  14 +-
 res/res_sip_registrar_expire.c               | 227 +++++
 res/res_sip_sdp_rtp.c                        | 188 +++-
 res/res_sip_session.c                        | 190 +++-
 res/res_sip_session.exports.in               |   3 +
 res/res_sip_transport_websocket.c            | 402 +++++++++
 42 files changed, 6425 insertions(+), 648 deletions(-)
 delete mode 100644 channels/sip/include/sdp_crypto.h
 delete mode 100644 channels/sip/include/srtp.h
 delete mode 100644 channels/sip/srtp.c
 create mode 100644 include/asterisk/res_sip_exten_state.h
 create mode 100644 include/asterisk/sdp_srtp.h
 rename channels/sip/sdp_crypto.c => main/sdp_srtp.c (68%)
 create mode 100644 res/res_sip/security_events.c
 create mode 100644 res/res_sip_diversion.c
 create mode 100644 res/res_sip_endpoint_identifier_anonymous.c
 create mode 100644 res/res_sip_exten_state.c
 create mode 100644 res/res_sip_exten_state.exports.in
 create mode 100644 res/res_sip_messaging.c
 create mode 100644 res/res_sip_one_touch_record_info.c
 create mode 100644 res/res_sip_pidf.c
 create mode 100644 res/res_sip_refer.c
 create mode 100644 res/res_sip_registrar_expire.c
 create mode 100644 res/res_sip_transport_websocket.c

diff --git a/channels/chan_gulp.c b/channels/chan_gulp.c
index 9e939a0f44..6a80651cf8 100644
--- a/channels/chan_gulp.c
+++ b/channels/chan_gulp.c
@@ -53,6 +53,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/musiconhold.h"
 #include "asterisk/causes.h"
 #include "asterisk/taskprocessor.h"
+#include "asterisk/dsp.h"
 #include "asterisk/stasis_endpoints.h"
 #include "asterisk/stasis_channels.h"
 
@@ -79,6 +80,19 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 			<para>Returns a properly formatted dial string for dialing all contacts on an AOR.</para>
 		</description>
 	</function>
+	<function name="GULP_MEDIA_OFFER" language="en_US">
+		<synopsis>
+			Media and codec offerings to be set on an outbound SIP channel prior to dialing.
+		</synopsis>
+		<syntax>
+			<parameter name="media" required="true">
+				<para>types of media offered</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Returns the codecs offered based upon the media choice</para>
+		</description>
+	</function>
  ***/
 
 static const char desc[] = "Gulp SIP Channel";
@@ -128,6 +142,7 @@ static int gulp_answer(struct ast_channel *ast);
 static struct ast_frame *gulp_read(struct ast_channel *ast);
 static int gulp_write(struct ast_channel *ast, struct ast_frame *f);
 static int gulp_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
+static int gulp_transfer(struct ast_channel *ast, const char *target);
 static int gulp_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
 static int gulp_devicestate(const char *data);
 
@@ -147,6 +162,7 @@ static struct ast_channel_tech gulp_tech = {
 	.write_video = gulp_write,
 	.exception = gulp_read,
 	.indicate = gulp_indicate,
+	.transfer = gulp_transfer,
 	.fixup = gulp_fixup,
 	.devicestate = gulp_devicestate,
 	.properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER
@@ -255,6 +271,105 @@ static struct ast_custom_function gulp_dial_contacts_function = {
 	.read = gulp_dial_contacts,
 };
 
+static int media_offer_read_av(struct ast_sip_session *session, char *buf,
+			       size_t len, enum ast_format_type media_type)
+{
+	int i, size = 0;
+	struct ast_format fmt;
+	const char *name;
+
+	for (i = 0; ast_codec_pref_index(&session->override_prefs, i, &fmt); ++i) {
+		if (AST_FORMAT_GET_TYPE(fmt.id) != media_type) {
+			continue;
+		}
+
+		name = ast_getformatname(&fmt);
+
+		if (ast_strlen_zero(name)) {
+			ast_log(LOG_WARNING, "GULP_MEDIA_OFFER unrecognized format %s\n", name);
+			continue;
+		}
+
+		/* add one since we'll include a comma */
+		size = strlen(name) + 1;
+		len -= size;
+		if ((len) < 0) {
+			break;
+		}
+
+		/* no reason to use strncat here since we have already ensured buf has
+                   enough space, so strcat can be safely used */
+		strcat(buf, name);
+		strcat(buf, ",");
+	}
+
+	if (size) {
+		/* remove the extra comma */
+		buf[strlen(buf) - 1] = '\0';
+	}
+	return 0;
+}
+
+struct media_offer_data {
+	struct ast_sip_session *session;
+	enum ast_format_type media_type;
+	const char *value;
+};
+
+static int media_offer_write_av(void *obj)
+{
+	struct media_offer_data *data = obj;
+	int i;
+	struct ast_format fmt;
+	/* remove all of the given media type first */
+	for (i = 0; ast_codec_pref_index(&data->session->override_prefs, i, &fmt); ++i) {
+		if (AST_FORMAT_GET_TYPE(fmt.id) == data->media_type) {
+			ast_codec_pref_remove(&data->session->override_prefs, &fmt);
+		}
+	}
+	ast_format_cap_remove_bytype(data->session->req_caps, data->media_type);
+	ast_parse_allow_disallow(&data->session->override_prefs, data->session->req_caps, data->value, 1);
+
+	return 0;
+}
+
+static int media_offer_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+	struct gulp_pvt *pvt = ast_channel_tech_pvt(chan);
+
+	if (!strcmp(data, "audio")) {
+		return media_offer_read_av(pvt->session, buf, len, AST_FORMAT_TYPE_AUDIO);
+	} else if (!strcmp(data, "video")) {
+		return media_offer_read_av(pvt->session, buf, len, AST_FORMAT_TYPE_VIDEO);
+	}
+
+	return 0;
+}
+
+static int media_offer_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
+{
+	struct gulp_pvt *pvt = ast_channel_tech_pvt(chan);
+
+	struct media_offer_data mdata = {
+		.session = pvt->session,
+		.value = value
+	};
+
+	if (!strcmp(data, "audio")) {
+		mdata.media_type = AST_FORMAT_TYPE_AUDIO;
+	} else if (!strcmp(data, "video")) {
+		mdata.media_type = AST_FORMAT_TYPE_VIDEO;
+	}
+
+	return ast_sip_push_task_synchronous(pvt->session->serializer, media_offer_write_av, &mdata);
+}
+
+static struct ast_custom_function media_offer_function = {
+	.name = "GULP_MEDIA_OFFER",
+	.read = media_offer_read,
+	.write = media_offer_write
+};
+
 /*! \brief Function called by RTP engine to get local audio RTP peer */
 static enum ast_rtp_glue_result gulp_get_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance)
 {
@@ -402,7 +517,11 @@ static int gulp_set_rtp_peer(struct ast_channel *chan,
 
 	if (changed) {
 		ao2_ref(session, +1);
-		ast_sip_push_task(session->serializer, send_direct_media_request, session);
+
+
+		if (ast_sip_push_task(session->serializer, send_direct_media_request, session)) {
+			ao2_cleanup(session);
+		}
 	}
 
 	return 0;
@@ -467,6 +586,12 @@ static struct ast_channel *gulp_new(struct ast_sip_session *session, int state,
 	ast_channel_exten_set(chan, S_OR(exten, "s"));
 	ast_channel_priority_set(chan, 1);
 
+	ast_channel_callgroup_set(chan, session->endpoint->callgroup);
+	ast_channel_pickupgroup_set(chan, session->endpoint->pickupgroup);
+
+	ast_channel_named_callgroups_set(chan, session->endpoint->named_callgroups);
+	ast_channel_named_pickupgroups_set(chan, session->endpoint->named_pickupgroups);
+
 	ast_endpoint_add_channel(session->endpoint->persistent, chan);
 
 	return chan;
@@ -513,6 +638,7 @@ static int gulp_answer(struct ast_channel *ast)
 static struct ast_frame *gulp_read(struct ast_channel *ast)
 {
 	struct gulp_pvt *pvt = ast_channel_tech_pvt(ast);
+	struct ast_sip_session *session = pvt->session;
 	struct ast_frame *f;
 	struct ast_sip_session_media *media = NULL;
 	int rtcp = 0;
@@ -539,14 +665,27 @@ static struct ast_frame *gulp_read(struct ast_channel *ast)
 		return &ast_null_frame;
 	}
 
-	f = ast_rtp_instance_read(media->rtp, rtcp);
+	if (!(f = ast_rtp_instance_read(media->rtp, rtcp))) {
+		return f;
+	}
+
+	if (f->frametype != AST_FRAME_VOICE) {
+		return f;
+	}
 
-	if (f && f->frametype == AST_FRAME_VOICE) {
-		if (!(ast_format_cap_iscompatible(ast_channel_nativeformats(ast), &f->subclass.format))) {
-			ast_debug(1, "Oooh, format changed to %s\n", ast_getformatname(&f->subclass.format));
-			ast_format_cap_set(ast_channel_nativeformats(ast), &f->subclass.format);
-			ast_set_read_format(ast, ast_channel_readformat(ast));
-			ast_set_write_format(ast, ast_channel_writeformat(ast));
+	if (!(ast_format_cap_iscompatible(ast_channel_nativeformats(ast), &f->subclass.format))) {
+		ast_debug(1, "Oooh, format changed to %s\n", ast_getformatname(&f->subclass.format));
+		ast_format_cap_set(ast_channel_nativeformats(ast), &f->subclass.format);
+		ast_set_read_format(ast, ast_channel_readformat(ast));
+		ast_set_write_format(ast, ast_channel_writeformat(ast));
+	}
+
+	if (session->dsp) {
+		f = ast_dsp_process(ast, session->dsp, f);
+
+		if (f && (f->frametype == AST_FRAME_DTMF)) {
+			ast_debug(3, "* Detected inband DTMF '%c' on '%s'\n", f->subclass.integer,
+				ast_channel_name(ast));
 		}
 	}
 
@@ -769,7 +908,7 @@ static int transmit_info_with_vidupdate(void *data)
 		.body_text = xml
 	};
 
-	struct ast_sip_session *session = data;
+	RAII_VAR(struct ast_sip_session *, session, data, ao2_cleanup);
 	struct pjsip_tx_data *tdata;
 
 	if (ast_sip_create_request("INFO", session->inv_session->dlg, session->endpoint, NULL, &tdata)) {
@@ -785,6 +924,40 @@ static int transmit_info_with_vidupdate(void *data)
 	return 0;
 }
 
+/*! \brief Update connected line information */
+static int update_connected_line_information(void *data)
+{
+	RAII_VAR(struct ast_sip_session *, session, data, ao2_cleanup);
+
+	if ((ast_channel_state(session->channel) != AST_STATE_UP) && (session->inv_session->role == PJSIP_UAS_ROLE)) {
+		int response_code = 0;
+
+		if (ast_channel_state(session->channel) == AST_STATE_RING) {
+			response_code = !session->endpoint->inband_progress ? 180 : 183;
+		} else if (ast_channel_state(session->channel) == AST_STATE_RINGING) {
+			response_code = 183;
+		}
+
+		if (response_code) {
+			struct pjsip_tx_data *packet = NULL;
+
+			if (pjsip_inv_answer(session->inv_session, response_code, NULL, NULL, &packet) == PJ_SUCCESS) {
+				ast_sip_session_send_response(session, packet);
+			}
+		}
+	} else {
+		enum ast_sip_session_refresh_method method = session->endpoint->connected_line_method;
+
+		if (session->inv_session->invite_tsx && (session->inv_session->options & PJSIP_INV_SUPPORT_UPDATE)) {
+			method = AST_SIP_SESSION_REFRESH_METHOD_UPDATE;
+		}
+
+		ast_sip_session_refresh(session, NULL, NULL, method, 0);
+	}
+
+	return 0;
+}
+
 /*! \brief Function called by core to ask the channel to indicate some sort of condition */
 static int gulp_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
 {
@@ -797,7 +970,12 @@ static int gulp_indicate(struct ast_channel *ast, int condition, const void *dat
 	switch (condition) {
 	case AST_CONTROL_RINGING:
 		if (ast_channel_state(ast) == AST_STATE_RING) {
-			response_code = 180;
+			if (session->endpoint->inband_progress) {
+				response_code = 183;
+				res = -1;
+			} else {
+				response_code = 180;
+			}
 		} else {
 			res = -1;
 		}
@@ -841,9 +1019,20 @@ static int gulp_indicate(struct ast_channel *ast, int condition, const void *dat
 	case AST_CONTROL_VIDUPDATE:
 		media = pvt->media[SIP_MEDIA_VIDEO];
 		if (media && media->rtp) {
-			ast_sip_push_task(session->serializer, transmit_info_with_vidupdate, session);
-		} else
+			ao2_ref(session, +1);
+
+			if (ast_sip_push_task(session->serializer, transmit_info_with_vidupdate, session)) {
+				ao2_cleanup(session);
+			}
+		} else {
 			res = -1;
+		}
+		break;
+	case AST_CONTROL_CONNECTED_LINE:
+		ao2_ref(session, +1);
+		if (ast_sip_push_task(session->serializer, update_connected_line_information, session)) {
+			ao2_cleanup(session);
+		}
 		break;
 	case AST_CONTROL_UPDATE_RTP_PEER:
 	case AST_CONTROL_PVT_CAUSE_CODE:
@@ -858,6 +1047,13 @@ static int gulp_indicate(struct ast_channel *ast, int condition, const void *dat
 		break;
 	case AST_CONTROL_SRCCHANGE:
 		break;
+	case AST_CONTROL_REDIRECTING:
+		if (ast_channel_state(ast) != AST_STATE_UP) {
+			response_code = 181;
+		} else {
+			res = -1;
+		}
+		break;
 	case -1:
 		res = -1;
 		break;
@@ -867,16 +1063,12 @@ static int gulp_indicate(struct ast_channel *ast, int condition, const void *dat
 		break;
 	}
 
-	if (!res && response_code) {
+	if (response_code) {
 		struct indicate_data *ind_data = indicate_data_alloc(session, condition, response_code, data, datalen);
-		if (ind_data) {
-			res = ast_sip_push_task(session->serializer, indicate, ind_data);
-			if (res) {
-				ast_log(LOG_NOTICE, "Cannot send response code %d to endpoint %s. Could not queue task properly\n",
-						response_code, ast_sorcery_object_get_id(session->endpoint));
-				ao2_cleanup(ind_data);
-			}
-		} else {
+		if (!ind_data || ast_sip_push_task(session->serializer, indicate, ind_data)) {
+			ast_log(LOG_NOTICE, "Cannot send response code %d to endpoint %s. Could not queue task properly\n",
+					response_code, ast_sorcery_object_get_id(session->endpoint));
+			ao2_cleanup(ind_data);
 			res = -1;
 		}
 	}
@@ -884,6 +1076,130 @@ static int gulp_indicate(struct ast_channel *ast, int condition, const void *dat
 	return res;
 }
 
+struct transfer_data {
+	struct ast_sip_session *session;
+	char *target;
+};
+
+static void transfer_data_destroy(void *obj)
+{
+	struct transfer_data *trnf_data = obj;
+
+	ast_free(trnf_data->target);
+	ao2_cleanup(trnf_data->session);
+}
+
+static struct transfer_data *transfer_data_alloc(struct ast_sip_session *session, const char *target)
+{
+	struct transfer_data *trnf_data = ao2_alloc(sizeof(*trnf_data), transfer_data_destroy);
+
+	if (!trnf_data) {
+		return NULL;
+	}
+
+	if (!(trnf_data->target = ast_strdup(target))) {
+		ao2_ref(trnf_data, -1);
+		return NULL;
+	}
+
+	ao2_ref(session, +1);
+	trnf_data->session = session;
+
+	return trnf_data;
+}
+
+static void transfer_redirect(struct ast_sip_session *session, const char *target)
+{
+	pjsip_tx_data *packet;
+	enum ast_control_transfer message = AST_TRANSFER_SUCCESS;
+	pjsip_contact_hdr *contact;
+	pj_str_t tmp;
+
+	if (pjsip_inv_end_session(session->inv_session, 302, NULL, &packet) != PJ_SUCCESS) {
+		message = AST_TRANSFER_FAILED;
+		ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message));
+
+		return;
+	}
+
+	if (!(contact = pjsip_msg_find_hdr(packet->msg, PJSIP_H_CONTACT, NULL))) {
+		contact = pjsip_contact_hdr_create(packet->pool);
+	}
+
+	pj_strdup2_with_null(packet->pool, &tmp, target);
+	if (!(contact->uri = pjsip_parse_uri(packet->pool, tmp.ptr, tmp.slen, PJSIP_PARSE_URI_AS_NAMEADDR))) {
+		message = AST_TRANSFER_FAILED;
+		ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message));
+		pjsip_tx_data_dec_ref(packet);
+
+		return;
+	}
+	pjsip_msg_add_hdr(packet->msg, (pjsip_hdr *) contact);
+
+	ast_sip_session_send_response(session, packet);
+	ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message));
+}
+
+static void transfer_refer(struct ast_sip_session *session, const char *target)
+{
+	pjsip_evsub *sub;
+	enum ast_control_transfer message = AST_TRANSFER_SUCCESS;
+	pj_str_t tmp;
+	pjsip_tx_data *packet;
+
+	if (pjsip_xfer_create_uac(session->inv_session->dlg, NULL, &sub) != PJ_SUCCESS) {
+		message = AST_TRANSFER_FAILED;
+		ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message));
+
+		return;
+	}
+
+	if (pjsip_xfer_initiate(sub, pj_cstr(&tmp, target), &packet) != PJ_SUCCESS) {
+		message = AST_TRANSFER_FAILED;
+		ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message));
+		pjsip_evsub_terminate(sub, PJ_FALSE);
+
+		return;
+	}
+
+	pjsip_xfer_send_request(sub, packet);
+	ast_queue_control_data(session->channel, AST_CONTROL_TRANSFER, &message, sizeof(message));
+}
+
+static int transfer(void *data)
+{
+	struct transfer_data *trnf_data = data;
+
+	if (ast_channel_state(trnf_data->session->channel) == AST_STATE_RING) {
+		transfer_redirect(trnf_data->session, trnf_data->target);
+	} else {
+		transfer_refer(trnf_data->session, trnf_data->target);
+	}
+
+	ao2_ref(trnf_data, -1);
+	return 0;
+}
+
+/*! \brief Function called by core for Asterisk initiated transfer */
+static int gulp_transfer(struct ast_channel *chan, const char *target)
+{
+	struct gulp_pvt *pvt = ast_channel_tech_pvt(chan);
+	struct ast_sip_session *session = pvt->session;
+	struct transfer_data *trnf_data = transfer_data_alloc(session, target);
+
+	if (!trnf_data) {
+		return -1;
+	}
+
+	if (ast_sip_push_task(session->serializer, transfer, trnf_data)) {
+		ast_log(LOG_WARNING, "Error requesting transfer\n");
+		ao2_cleanup(trnf_data);
+		return -1;
+	}
+
+	return 0;
+}
+
 /*! \brief Function called by core to start a DTMF digit */
 static int gulp_digit_begin(struct ast_channel *chan, char digit)
 {
@@ -1014,18 +1330,18 @@ static int gulp_digit_end(struct ast_channel *ast, char digit, unsigned int dura
 
 static int call(void *data)
 {
-	pjsip_tx_data *packet;
 	struct ast_sip_session *session = data;
+	pjsip_tx_data *tdata;
+
+	int res = ast_sip_session_create_invite(session, &tdata);
 
-	if (pjsip_inv_invite(session->inv_session, &packet) != PJ_SUCCESS) {
+	if (res) {
 		ast_queue_hangup(session->channel);
 	} else {
-		ast_sip_session_send_request(session, packet);
+		ast_sip_session_send_request(session, tdata);
 	}
-
 	ao2_ref(session, -1);
-
-	return 0;
+	return res;
 }
 
 /*! \brief Function called by core to actually start calling a remote party */
@@ -1128,7 +1444,8 @@ static int hangup(void *data)
 	struct ast_sip_session *session = pvt->session;
 	int cause = h_data->cause;
 
-	if (((status = pjsip_inv_end_session(session->inv_session, cause ? cause : 603, NULL, &packet)) == PJ_SUCCESS) && packet) {
+	if (!session->defer_terminate &&
+		((status = pjsip_inv_end_session(session->inv_session, cause ? cause : 603, NULL, &packet)) == PJ_SUCCESS) && packet) {
 		if (packet->msg->type == PJSIP_RESPONSE_MSG) {
 			ast_sip_session_send_response(session, packet);
 		} else {
@@ -1255,9 +1572,66 @@ static struct ast_channel *gulp_request(const char *type, struct ast_format_cap
 	return session->channel;
 }
 
+struct sendtext_data {
+	struct ast_sip_session *session;
+	char text[0];
+};
+
+static void sendtext_data_destroy(void *obj)
+{
+	struct sendtext_data *data = obj;
+	ao2_ref(data->session, -1);
+}
+
+static struct sendtext_data* sendtext_data_create(struct ast_sip_session *session, const char *text)
+{
+	int size = strlen(text) + 1;
+	struct sendtext_data *data = ao2_alloc(sizeof(*data)+size, sendtext_data_destroy);
+
+	if (!data) {
+		return NULL;
+	}
+
+	data->session = session;
+	ao2_ref(data->session, +1);
+	ast_copy_string(data->text, text, size);
+	return data;
+}
+
+static int sendtext(void *obj)
+{
+	RAII_VAR(struct sendtext_data *, data, obj, ao2_cleanup);
+	pjsip_tx_data *tdata;
+
+	const struct ast_sip_body body = {
+		.type = "text",
+		.subtype = "plain",
+		.body_text = data->text
+	};
+
+	/* NOT ast_strlen_zero, because a zero-length message is specifically
+	 * allowed by RFC 3428 (See section 10, Examples) */
+	if (!data->text) {
+		return 0;
+	}
+
+	ast_sip_create_request("MESSAGE", data->session->inv_session->dlg, data->session->endpoint, NULL, &tdata);
+	ast_sip_add_body(tdata, &body);
+	ast_sip_send_request(tdata, data->session->inv_session->dlg, data->session->endpoint);
+
+	return 0;
+}
+
 /*! \brief Function called by core to send text on Gulp session */
 static int gulp_sendtext(struct ast_channel *ast, const char *text)
 {
+	struct gulp_pvt *pvt = ast_channel_tech_pvt(ast);
+	struct sendtext_data *data = sendtext_data_create(pvt->session, text);
+
+	if (!data || ast_sip_push_task(pvt->session->serializer, sendtext, data)) {
+		ao2_ref(data, -1);
+		return -1;
+	}
 	return 0;
 }
 
@@ -1391,7 +1765,6 @@ static void gulp_session_end(struct ast_sip_session *session)
 static int gulp_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
 {
 	pjsip_tx_data *packet = NULL;
-	int res = AST_PBX_FAILED;
 
 	if (session->channel) {
 		return 0;
@@ -1405,6 +1778,14 @@ static int gulp_incoming_request(struct ast_sip_session *session, struct pjsip_r
 		ast_log(LOG_ERROR, "Failed to allocate new GULP channel on incoming SIP INVITE\n");
 		return -1;
 	}
+	/* channel gets created on incoming request, but we wait to call start
+           so other supplements have a chance to run */
+	return 0;
+}
+
+static int pbx_start_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata)
+{
+	int res;
 
 	res = ast_pbx_start(session->channel);
 
@@ -1429,6 +1810,12 @@ static int gulp_incoming_request(struct ast_sip_session *session, struct pjsip_r
 	return (res == AST_PBX_SUCCESS) ? 0 : -1;
 }
 
+static struct ast_sip_session_supplement pbx_start_supplement = {
+	.method = "INVITE",
+	.priority = AST_SIP_SESSION_SUPPLEMENT_PRIORITY_LAST,
+	.incoming_request = pbx_start_incoming_request,
+};
+
 /*! \brief Function called when a response is received on the session */
 static void gulp_incoming_response(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
 {
@@ -1496,13 +1883,24 @@ static int load_module(void)
 		goto end;
 	}
 
+	if (ast_custom_function_register(&media_offer_function)) {
+		ast_log(LOG_WARNING, "Unable to register GULP_MEDIA_OFFER dialplan function\n");
+	}
+
 	if (ast_sip_session_register_supplement(&gulp_supplement)) {
 		ast_log(LOG_ERROR, "Unable to register Gulp supplement\n");
 		goto end;
 	}
 
+	if (ast_sip_session_register_supplement(&pbx_start_supplement)) {
+		ast_log(LOG_ERROR, "Unable to register Gulp pbx start supplement\n");
+		ast_sip_session_unregister_supplement(&gulp_supplement);
+		goto end;
+	}
+
 	if (ast_sip_session_register_supplement(&gulp_ack_supplement)) {
 		ast_log(LOG_ERROR, "Unable to register Gulp ACK supplement\n");
+		ast_sip_session_unregister_supplement(&pbx_start_supplement);
 		ast_sip_session_unregister_supplement(&gulp_supplement);
 		goto end;
 	}
@@ -1510,6 +1908,7 @@ static int load_module(void)
 	return 0;
 
 end:
+	ast_custom_function_unregister(&media_offer_function);
 	ast_custom_function_unregister(&gulp_dial_contacts_function);
 	ast_channel_unregister(&gulp_tech);
 	ast_rtp_glue_unregister(&gulp_rtp_glue);
@@ -1526,7 +1925,11 @@ static int reload(void)
 /*! \brief Unload the Gulp channel from Asterisk */
 static int unload_module(void)
 {
+	ast_custom_function_unregister(&media_offer_function);
+
 	ast_sip_session_unregister_supplement(&gulp_supplement);
+	ast_sip_session_unregister_supplement(&pbx_start_supplement);
+
 	ast_custom_function_unregister(&gulp_dial_contacts_function);
 	ast_channel_unregister(&gulp_tech);
 	ast_rtp_glue_unregister(&gulp_rtp_glue);
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index c207e24fec..689b43a05a 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -285,8 +285,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "sip/include/config_parser.h"
 #include "sip/include/reqresp_parser.h"
 #include "sip/include/sip_utils.h"
-#include "sip/include/srtp.h"
-#include "sip/include/sdp_crypto.h"
+#include "asterisk/sdp_srtp.h"
 #include "asterisk/ccss.h"
 #include "asterisk/xml.h"
 #include "sip/include/dialog.h"
@@ -1490,8 +1489,7 @@ static int handle_response_register(struct sip_pvt *p, int resp, const char *res
 static void handle_response(struct sip_pvt *p, int resp, const char *rest, struct sip_request *req, uint32_t seqno);
 
 /*------ SRTP Support -------- */
-static int setup_srtp(struct sip_srtp **srtp);
-static int process_crypto(struct sip_pvt *p, struct ast_rtp_instance *rtp, struct sip_srtp **srtp, const char *a);
+static int process_crypto(struct sip_pvt *p, struct ast_rtp_instance *rtp, struct ast_sdp_srtp **srtp, const char *a);
 
 /*------ T38 Support --------- */
 static int transmit_response_with_t38_sdp(struct sip_pvt *p, char *msg, struct sip_request *req, int retrans);
@@ -5918,7 +5916,7 @@ static void copy_socket_data(struct sip_socket *to_sock, const struct sip_socket
 }
 
 /*! \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 sip_srtp **srtp)
+static int dialog_initialize_dtls_srtp(const struct sip_pvt *dialog, struct ast_rtp_instance *rtp, struct ast_sdp_srtp **srtp)
 {
 	struct ast_rtp_engine_dtls *dtls;
 
@@ -5943,7 +5941,7 @@ static int dialog_initialize_dtls_srtp(const struct sip_pvt *dialog, struct ast_
 		return -1;
 	}
 
-	if (!(*srtp = sip_srtp_alloc())) {
+	if (!(*srtp = ast_sdp_srtp_alloc())) {
 		ast_log(LOG_ERROR, "Failed to create required SRTP structure on RTP instance '%p'\n",
 			rtp);
 		return -1;
@@ -6418,17 +6416,17 @@ static int sip_call(struct ast_channel *ast, const char *dest, int timeout)
 			ast_clear_flag(&p->flags[0], SIP_REINVITE);
 		}
 
-		if (p->rtp && !p->srtp && setup_srtp(&p->srtp) < 0) {
+		if (p->rtp && !p->srtp && !(p->srtp = ast_sdp_srtp_alloc())) {
 			ast_log(LOG_WARNING, "SRTP audio setup failed\n");
 			return -1;
 		}
 
-		if (p->vrtp && !p->vsrtp && setup_srtp(&p->vsrtp) < 0) {
+		if (p->vrtp && !p->vsrtp && !(p->vsrtp = ast_sdp_srtp_alloc())) {
 			ast_log(LOG_WARNING, "SRTP video setup failed\n");
 			return -1;
 		}
 
-		if (p->trtp && !p->tsrtp && setup_srtp(&p->tsrtp) < 0) {
+		if (p->trtp && !p->tsrtp && !(p->tsrtp = ast_sdp_srtp_alloc())) {
 			ast_log(LOG_WARNING, "SRTP text setup failed\n");
 			return -1;
 		}
@@ -6690,17 +6688,17 @@ void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist)
 	destroy_msg_headers(p);
 
 	if (p->srtp) {
-		sip_srtp_destroy(p->srtp);
+		ast_sdp_srtp_destroy(p->srtp);
 		p->srtp = NULL;
 	}
 
 	if (p->vsrtp) {
-		sip_srtp_destroy(p->vsrtp);
+		ast_sdp_srtp_destroy(p->vsrtp);
 		p->vsrtp = NULL;
 	}
 
 	if (p->tsrtp) {
-		sip_srtp_destroy(p->tsrtp);
+		ast_sdp_srtp_destroy(p->tsrtp);
 		p->tsrtp = NULL;
 	}
 
@@ -10154,7 +10152,7 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
 					secure_audio = 1;
 
 					if (p->srtp) {
-						ast_set_flag(p->srtp, SRTP_CRYPTO_OFFER_OK);
+						ast_set_flag(p->srtp, AST_SRTP_CRYPTO_OFFER_OK);
 					}
 				} else if (!strcmp(protocol, "RTP/SAVP") || !strcmp(protocol, "RTP/SAVPF")) {
 					secure_audio = 1;
@@ -10235,8 +10233,8 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
 				} else if (!strcmp(protocol, "UDP/TLS/RTP/SAVP") || !strcmp(protocol, "UDP/TLS/RTP/SAVPF")) {
 					secure_video = 1;
 
-					if (p->vsrtp || (p->vsrtp = sip_srtp_alloc())) {
-						ast_set_flag(p->vsrtp, SRTP_CRYPTO_OFFER_OK);
+					if (p->vsrtp || (p->vsrtp = ast_sdp_srtp_alloc())) {
+						ast_set_flag(p->vsrtp, AST_SRTP_CRYPTO_OFFER_OK);
 					}
 				} else if (!strcmp(protocol, "RTP/SAVP") || !strcmp(protocol, "RTP/SAVPF")) {
 					secure_video = 1;
@@ -10516,7 +10514,7 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
 		goto process_sdp_cleanup;
 	}
 
-	if (secure_audio && !(p->srtp && (ast_test_flag(p->srtp, SRTP_CRYPTO_OFFER_OK)))) {
+	if (secure_audio && !(p->srtp && (ast_test_flag(p->srtp, AST_SRTP_CRYPTO_OFFER_OK)))) {
 		ast_log(LOG_WARNING, "Can't provide secure audio requested in SDP offer\n");
 		res = -1;
 		goto process_sdp_cleanup;
@@ -10528,7 +10526,7 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
 		goto process_sdp_cleanup;
 	}
 
-	if (secure_video && !(p->vsrtp && (ast_test_flag(p->vsrtp, SRTP_CRYPTO_OFFER_OK)))) {
+	if (secure_video && !(p->vsrtp && (ast_test_flag(p->vsrtp, AST_SRTP_CRYPTO_OFFER_OK)))) {
 		ast_log(LOG_WARNING, "Can't provide secure video requested in SDP offer\n");
 		res = -1;
 		goto process_sdp_cleanup;
@@ -12993,52 +12991,20 @@ static void get_our_media_address(struct sip_pvt *p, int needvideo, int needtext
 	}
 }
 
-static void get_crypto_attrib(struct sip_pvt *p, struct sip_srtp *srtp, const char **a_crypto)
+static char *crypto_get_attrib(struct ast_sdp_srtp *srtp, int dtls_enabled, int default_taglen_32)
 {
-	int taglen = 80;
+	char *a_crypto;
+	char *orig_crypto;
 
-	/* Set encryption properties */
-	if (srtp) {
-		if (!srtp->crypto) {
-			srtp->crypto = sdp_crypto_setup();
-		}
-
-		if (p->dtls_cfg.enabled) {
-			/* If DTLS-SRTP is enabled the key details will be pulled from TLS */
-			return;
-		}
-
-		/* set the key length based on INVITE or settings */
-		if (ast_test_flag(srtp, SRTP_CRYPTO_TAG_80)) {
-			taglen = 80;
-		} else if (ast_test_flag(&p->flags[2], SIP_PAGE3_SRTP_TAG_32) ||
-		    ast_test_flag(srtp, SRTP_CRYPTO_TAG_32)) {
-			taglen = 32;
-		}
-
-		if (srtp->crypto && (sdp_crypto_offer(srtp->crypto, taglen) >= 0)) {
-			*a_crypto = sdp_crypto_attrib(srtp->crypto);
-		}
-
-		if (!*a_crypto) {
-			ast_log(LOG_WARNING, "No SRTP key management enabled\n");
-		}
+	if (!srtp) {
+		return NULL;
 	}
-}
 
-static char *get_sdp_rtp_profile(const struct sip_pvt *p, unsigned int secure, struct ast_rtp_instance *instance)
-{
-	struct ast_rtp_engine_dtls *dtls;
-
-	if ((dtls = ast_rtp_instance_get_dtls(instance)) && dtls->active(instance)) {
-		return ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF) ? "UDP/TLS/RTP/SAVPF" : "UDP/TLS/RTP/SAVP";
-	} else {
-		if (ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)) {
-			return secure ? "RTP/SAVPF" : "RTP/AVPF";
-		} else {
-			return secure ? "RTP/SAVP" : "RTP/AVP";
-		}
+	orig_crypto = ast_strdupa(ast_sdp_srtp_get_attrib(srtp, dtls_enabled, default_taglen_32));
+	if (ast_asprintf(&a_crypto, "a=crypto:%s\r\n", orig_crypto) == -1) {
+		return NULL;
 	}
+	return a_crypto;
 }
 
 /*! \brief Add Session Description Protocol message
@@ -13079,9 +13045,9 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
 	struct ast_str *a_video = ast_str_create(256); /* Attributes for video */
 	struct ast_str *a_text = ast_str_create(256);  /* Attributes for text */
 	struct ast_str *a_modem = ast_str_alloca(1024); /* Attributes for modem */
-	const char *a_crypto = NULL;
-	const char *v_a_crypto = NULL;
-	const char *t_a_crypto = NULL;
+	RAII_VAR(char *, a_crypto, NULL, ast_free);
+	RAII_VAR(char *, v_a_crypto, NULL, ast_free);
+	RAII_VAR(char *, t_a_crypto, NULL, ast_free);
 
 	int x;
 	struct ast_format tmp_fmt;
@@ -13199,9 +13165,11 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
 		/* Ok, we need video. Let's add what we need for video and set codecs.
 		   Video is handled differently than audio since we can not transcode. */
 		if (needvideo) {
-			get_crypto_attrib(p, p->vsrtp, &v_a_crypto);
+			v_a_crypto = crypto_get_attrib(p->vsrtp, p->dtls_cfg.enabled,
+				ast_test_flag(&p->flags[2], SIP_PAGE3_SRTP_TAG_32));
 			ast_str_append(&m_video, 0, "m=video %d %s", ast_sockaddr_port(&vdest),
-				       get_sdp_rtp_profile(p, v_a_crypto ? 1 : 0, p->vrtp));
+				ast_sdp_get_rtp_profile(v_a_crypto ? 1 : 0, p->vrtp,
+					ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)));
 
 			/* Build max bitrate string */
 			if (p->maxcallbitrate)
@@ -13224,9 +13192,11 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
 		if (needtext) {
 			if (sipdebug_text)
 				ast_verbose("Lets set up the text sdp\n");
-			get_crypto_attrib(p, p->tsrtp, &t_a_crypto);
+			t_a_crypto = crypto_get_attrib(p->tsrtp, p->dtls_cfg.enabled,
+				ast_test_flag(&p->flags[2], SIP_PAGE3_SRTP_TAG_32));
 			ast_str_append(&m_text, 0, "m=text %d %s", ast_sockaddr_port(&tdest),
-				       get_sdp_rtp_profile(p, t_a_crypto ? 1 : 0, p->trtp));
+				ast_sdp_get_rtp_profile(t_a_crypto ? 1 : 0, p->trtp,
+					ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)));
 			if (debug) {  /* XXX should I use tdest below ? */
 				ast_verbose("Text is at %s\n", ast_sockaddr_stringify(&taddr));
 			}
@@ -13245,9 +13215,11 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
 		/* We break with the "recommendation" and send our IP, in order that our
 		   peer doesn't have to ast_gethostbyname() us */
 
-		get_crypto_attrib(p, p->srtp, &a_crypto);
+		a_crypto = crypto_get_attrib(p->srtp, p->dtls_cfg.enabled,
+			ast_test_flag(&p->flags[2], SIP_PAGE3_SRTP_TAG_32));
 		ast_str_append(&m_audio, 0, "m=audio %d %s", ast_sockaddr_port(&dest),
-			       get_sdp_rtp_profile(p, a_crypto ? 1 : 0, p->rtp));
+			ast_sdp_get_rtp_profile(a_crypto ? 1 : 0, p->rtp,
+				ast_test_flag(&p->flags[2], SIP_PAGE3_USE_AVPF)));
 
 		/* Now, start adding audio codecs. These are added in this order:
 		   - First what was requested by the calling channel
@@ -25767,7 +25739,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
 				transmit_response_with_t38_sdp(p, "200 OK", req, (reinvite ? XMIT_RELIABLE : (req->ignore ?  XMIT_UNRELIABLE : XMIT_CRITICAL)));
 			} else if ((p->t38.state == T38_DISABLED) || (p->t38.state == T38_REJECTED)) {
 				/* If this is not a re-invite or something to ignore - it's critical */
-				if (p->srtp && !ast_test_flag(p->srtp, SRTP_CRYPTO_OFFER_OK)) {
+				if (p->srtp && !ast_test_flag(p->srtp, AST_SRTP_CRYPTO_OFFER_OK)) {
 					ast_log(LOG_WARNING, "Target does not support required crypto\n");
 					transmit_response_reliable(p, "488 Not Acceptable Here (crypto)", req);
 				} else {
@@ -32791,22 +32763,7 @@ static void sip_send_all_mwi_subscriptions(void)
 	} while (0));
 }
 
-/* SRTP */
-static int setup_srtp(struct sip_srtp **srtp)
-{
-	if (!ast_rtp_engine_srtp_is_registered()) {
-		ast_debug(1, "No SRTP module loaded, can't setup SRTP session.\n");
-		return -1;
-	}
-
-	if (!(*srtp = sip_srtp_alloc())) { /* Allocate SRTP data structure */
-		return -1;
-	}
-
-	return 0;
-}
-
-static int process_crypto(struct sip_pvt *p, struct ast_rtp_instance *rtp, struct sip_srtp **srtp, const char *a)
+static int process_crypto(struct sip_pvt *p, struct ast_rtp_instance *rtp, struct ast_sdp_srtp **srtp, const char *a)
 {
 	struct ast_rtp_engine_dtls *dtls;
 
@@ -32819,27 +32776,28 @@ static int process_crypto(struct sip_pvt *p, struct ast_rtp_instance *rtp, struc
 	if (strncasecmp(a, "crypto:", 7)) {
 		return FALSE;
 	}
+	/* skip "crypto:" */
+	a += strlen("crypto:");
+
 	if (!*srtp) {
 		if (ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
 			ast_log(LOG_WARNING, "Ignoring unexpected crypto attribute in SDP answer\n");
 			return FALSE;
 		}
 
-		if (setup_srtp(srtp) < 0) {
+		if (!(*srtp = ast_sdp_srtp_alloc())) {
 			return FALSE;
 		}
 	}
 
-	if (!(*srtp)->crypto && !((*srtp)->crypto = sdp_crypto_setup())) {
+	if (!(*srtp)->crypto && !((*srtp)->crypto = ast_sdp_crypto_alloc())) {
 		return FALSE;
 	}
 
-	if (sdp_crypto_process((*srtp)->crypto, a, rtp, *srtp) < 0) {
+	if (ast_sdp_crypto_process(rtp, *srtp, a) < 0) {
 		return FALSE;
 	}
 
-	ast_set_flag(*srtp, SRTP_CRYPTO_OFFER_OK);
-
 	if ((dtls = ast_rtp_instance_get_dtls(rtp))) {
 		dtls->stop(rtp);
 		p->dtls_cfg.enabled = 0;
diff --git a/channels/sip/include/sdp_crypto.h b/channels/sip/include/sdp_crypto.h
deleted file mode 100644
index da1035e878..0000000000
--- a/channels/sip/include/sdp_crypto.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2006 - 2007, Mikael Magnusson
- *
- * Mikael Magnusson <mikma@users.sourceforge.net>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file sdp_crypto.h
- *
- * \brief SDP Security descriptions
- *
- * Specified in RFC 4568
- *
- * \author Mikael Magnusson <mikma@users.sourceforge.net>
- */
-
-#ifndef _SDP_CRYPTO_H
-#define _SDP_CRYPTO_H
-
-#include <asterisk/rtp_engine.h>
-
-struct sdp_crypto;
-struct sip_srtp;
-
-/*! \brief Initialize an return an sdp_crypto struct
- *
- * \details
- * This function allocates a new sdp_crypto struct and initializes its values
- *
- * \retval NULL on failure
- * \retval a pointer to a  new sdp_crypto structure
- */
-struct sdp_crypto *sdp_crypto_setup(void);
-
-/*! \brief Destroy a previously allocated sdp_crypto struct */
-void sdp_crypto_destroy(struct sdp_crypto *crypto);
-
-/*! \brief Parse the a=crypto line from SDP and set appropriate values on the
- * sdp_crypto struct.
- *
- * \param p A valid sdp_crypto struct
- * \param attr the a:crypto line from SDP
- * \param rtp The rtp instance associated with the SDP being parsed
- * \param srtp SRTP structure
- *
- * \retval 0 success
- * \retval nonzero failure
- */
-int sdp_crypto_process(struct sdp_crypto *p, const char *attr, struct ast_rtp_instance *rtp, struct sip_srtp *srtp);
-
-
-/*! \brief Generate an SRTP a=crypto offer
- *
- * \details
- * The offer is stored on the sdp_crypto struct in a_crypto
- *
- * \param p A valid sdp_crypto struct
- * \param taglen Length
- *
- * \retval 0 success
- * \retval nonzero failure
- */
-int sdp_crypto_offer(struct sdp_crypto *p, int taglen);
-
-
-/*! \brief Return the a_crypto value of the sdp_crypto struct
- *
- * \param p An sdp_crypto struct that has had sdp_crypto_offer called
- *
- * \retval The value of the a_crypto for p
- */
-const char *sdp_crypto_attrib(struct sdp_crypto *p);
-
-#endif	/* _SDP_CRYPTO_H */
diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h
index 0adde37f20..8b4672b25d 100644
--- a/channels/sip/include/sip.h
+++ b/channels/sip/include/sip.h
@@ -1165,9 +1165,9 @@ struct sip_pvt {
 	AST_LIST_HEAD_NOLOCK(request_queue, sip_request) request_queue; /*!< Requests that arrived but could not be processed immediately */
 	struct sip_invite_param *options;   /*!< Options for INVITE */
 	struct sip_st_dlg *stimer;          /*!< SIP Session-Timers */
-	struct sip_srtp *srtp;              /*!< Structure to hold Secure RTP session data for audio */
-	struct sip_srtp *vsrtp;             /*!< Structure to hold Secure RTP session data for video */
-	struct sip_srtp *tsrtp;             /*!< Structure to hold Secure RTP session data for text */
+	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 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) */
diff --git a/channels/sip/include/srtp.h b/channels/sip/include/srtp.h
deleted file mode 100644
index a4ded62ca9..0000000000
--- a/channels/sip/include/srtp.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2006 - 2007, Mikael Magnusson
- *
- * Mikael Magnusson <mikma@users.sourceforge.net>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file srtp.h
- *
- * \brief SIP Secure RTP (SRTP)
- *
- * Specified in RFC 3711
- *
- * \author Mikael Magnusson <mikma@users.sourceforge.net>
- */
-
-#ifndef _SIP_SRTP_H
-#define _SIP_SRTP_H
-
-#include "sdp_crypto.h"
-
-/* SRTP flags */
-#define SRTP_ENCR_OPTIONAL	(1 << 1)	/* SRTP encryption optional */
-#define SRTP_CRYPTO_ENABLE	(1 << 2)
-#define SRTP_CRYPTO_OFFER_OK	(1 << 3)
-#define SRTP_CRYPTO_TAG_32	(1 << 4)
-#define SRTP_CRYPTO_TAG_80	(1 << 5)
-
-/*! \brief structure for secure RTP audio */
-struct sip_srtp {
-	unsigned int flags;
-	struct sdp_crypto *crypto;
-};
-
-/*!
- * \brief allocate a sip_srtp structure
- * \retval a new malloc'd sip_srtp structure on success
- * \retval NULL on failure
-*/
-struct sip_srtp *sip_srtp_alloc(void);
-
-/*!
- * \brief free a sip_srtp structure
- * \param srtp a sip_srtp structure
-*/
-void sip_srtp_destroy(struct sip_srtp *srtp);
-
-#endif	/* _SIP_SRTP_H */
diff --git a/channels/sip/srtp.c b/channels/sip/srtp.c
deleted file mode 100644
index 8b2718fc35..0000000000
--- a/channels/sip/srtp.c
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2006 - 2007, Mikael Magnusson
- *
- * Mikael Magnusson <mikma@users.sourceforge.net>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file srtp.c
- *
- * \brief SIP Secure RTP (SRTP)
- *
- * Specified in RFC 3711
- *
- * \author Mikael Magnusson <mikma@users.sourceforge.net>
- */
-
-/*** MODULEINFO
-	<support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include "asterisk/utils.h"
-#include "include/srtp.h"
-
-struct sip_srtp *sip_srtp_alloc(void)
-{
-	struct sip_srtp *srtp;
-
-	srtp = ast_calloc(1, sizeof(*srtp));
-
-	return srtp;
-}
-
-void sip_srtp_destroy(struct sip_srtp *srtp)
-{
-	if (srtp->crypto) {
-		sdp_crypto_destroy(srtp->crypto);
-	}
-	srtp->crypto = NULL;
-	ast_free(srtp);
-}
diff --git a/configs/res_sip.conf.sample b/configs/res_sip.conf.sample
index dd8da8dffc..34cd6d6458 100644
--- a/configs/res_sip.conf.sample
+++ b/configs/res_sip.conf.sample
@@ -22,3 +22,5 @@ dtmfmode=rfc4733          ; Supported DTMF modes are rfc4733, inband, info, and
 ;rtp_ipv6=yes             ; Force IPv6 for RTP transport
 ;rtp_symmetric=yes        ; Enable symmetric RTP support
 ;use_ptime=yes            ; Whether to use the ptime value received from the endpoint or not
+;media_encryption=no      ; Options for media encryption are no, and sdes
+;use_avpf=no              ; Whether to force usage of AVPF transport for this endpoint
diff --git a/include/asterisk/res_sip.h b/include/asterisk/res_sip.h
index b48ed9f821..7c486aa689 100644
--- a/include/asterisk/res_sip.h
+++ b/include/asterisk/res_sip.h
@@ -139,6 +139,47 @@ struct ast_sip_contact {
 	);
 	/*! Absolute time that this contact is no longer valid after */
 	struct timeval expiration_time;
+	/*! Frequency to send OPTIONS requests to contact. 0 is disabled. */
+	unsigned int qualify_frequency;
+	/*! If true authenticate the qualify if needed */
+	int authenticate_qualify;
+};
+
+#define CONTACT_STATUS "contact_status"
+
+/*!
+ * \brief Status type for a contact.
+ */
+enum ast_sip_contact_status_type {
+	UNAVAILABLE,
+	AVAILABLE
+};
+
+/*!
+ * \brief A contact's status.
+ *
+ * \detail Maintains a contact's current status and round trip time
+ *         if available.
+ */
+struct ast_sip_contact_status {
+	SORCERY_OBJECT(details);
+	/*! Current status for a contact (default - unavailable) */
+	enum ast_sip_contact_status_type status;
+	/*! The round trip start time set before sending a qualify request */
+	struct timeval rtt_start;
+	/*! The round trip time in microseconds */
+	int64_t rtt;
+};
+
+/*!
+ * \brief A transport to be used for messages to a contact
+ */
+struct ast_sip_contact_transport {
+	AST_DECLARE_STRING_FIELDS(
+		/*! Full URI of the contact */
+		AST_STRING_FIELD(uri);
+	);
+	pjsip_transport *transport;
 };
 
 /*!
@@ -157,6 +198,10 @@ struct ast_sip_aor {
 	unsigned int maximum_expiration;
 	/*! Default contact expiration if one is not provided in the contact */
 	unsigned int default_expiration;
+	/*! Frequency to send OPTIONS requests to AOR contacts. 0 is disabled. */
+	unsigned int qualify_frequency;
+	/*! If true authenticate the qualify if needed */
+	int authenticate_qualify;
 	/*! Maximum number of external contacts, 0 to disable */
 	unsigned int max_contacts;
 	/*! Whether to remove any existing contacts not related to an incoming REGISTER when it comes in */
@@ -245,6 +290,17 @@ enum ast_sip_direct_media_glare_mitigation {
 	AST_SIP_DIRECT_MEDIA_GLARE_MITIGATION_INCOMING,
 };
 
+enum ast_sip_session_media_encryption {
+	/*! Invalid media encryption configuration */
+	AST_SIP_MEDIA_TRANSPORT_INVALID = 0,
+	/*! Do not allow any encryption of session media */
+	AST_SIP_MEDIA_ENCRYPT_NONE,
+	/*! Offer SDES-encrypted session media */
+	AST_SIP_MEDIA_ENCRYPT_SDES,
+	/*! Offer encrypted session media with datagram TLS key exchange */
+	AST_SIP_MEDIA_ENCRYPT_DTLS,
+};
+
 /*!
  * \brief An entity with which Asterisk communicates
  */
@@ -306,14 +362,14 @@ struct ast_sip_endpoint {
 	unsigned int sess_expires;
 	/*! List of outbound registrations */
 	AST_LIST_HEAD_NOLOCK(, ast_sip_registration) registrations;
-	/*! Frequency to send OPTIONS requests to endpoint. 0 is disabled. */
-	unsigned int qualify_frequency;
 	/*! Method(s) by which the endpoint should be identified. */
 	enum ast_sip_endpoint_identifier_type ident_method;
 	/*! Boolean indicating if direct_media is permissible */
 	unsigned int direct_media;
 	/*! When using direct media, which method should be used */
 	enum ast_sip_session_refresh_method direct_media_method;
+	/*! When performing connected line update, which method should be used */
+	enum ast_sip_session_refresh_method connected_line_method;
 	/*! Take steps to mitigate glare for direct media */
 	enum ast_sip_direct_media_glare_mitigation direct_media_glare_mitigation;
 	/*! Do not attempt direct media session refreshes if a media NAT is detected */
@@ -326,8 +382,26 @@ struct ast_sip_endpoint {
 	unsigned int send_pai;
 	/*! Do we send Remote-Party-ID headers to this endpoint? */
 	unsigned int send_rpid;
+	/*! Do we add Diversion headers to applicable outgoing requests/responses? */
+	unsigned int send_diversion;
 	/*! Should unsolicited MWI be aggregated into a single NOTIFY? */
 	unsigned int aggregate_mwi;
+	/*! Do we use media encryption? what type? */
+	enum ast_sip_session_media_encryption media_encryption;
+	/*! Do we use AVPF exclusively for this endpoint? */
+	unsigned int use_avpf;
+	/*! Is one-touch recording permitted? */
+	unsigned int one_touch_recording;
+	/*! Boolean indicating if ringing should be sent as inband progress */
+	unsigned int inband_progress;
+	/*! Call group */
+	ast_group_t callgroup;
+	/*! Pickup group */
+	ast_group_t pickupgroup;
+	/*! Named call group */
+	struct ast_namedgroups *named_callgroups;
+	/*! Named pickup group */
+	struct ast_namedgroups *named_pickupgroups;
 	/*! Pointer to the persistent Asterisk endpoint */
 	struct ast_endpoint *persistent;
 	/*! The number of channels at which busy device state is returned */
@@ -552,6 +626,16 @@ struct ast_sorcery *ast_sip_get_sorcery(void);
  */
 int ast_sip_initialize_sorcery_transport(struct ast_sorcery *sorcery);
 
+/*!
+ * \brief Initialize qualify support on a sorcery instance
+ *
+ * \param sorcery The sorcery instance
+ *
+ * \retval -1 failure
+ * \retval 0 success
+ */
+int ast_sip_initialize_sorcery_qualify(struct ast_sorcery *sorcery);
+
 /*!
  * \brief Initialize location support on a sorcery instance
  *
@@ -610,6 +694,37 @@ struct ast_sip_contact *ast_sip_location_retrieve_contact_from_aor_list(const ch
  */
 struct ast_sip_contact *ast_sip_location_retrieve_contact(const char *contact_name);
 
+/*!
+ * \brief Add a transport for a contact to use
+ */
+
+void ast_sip_location_add_contact_transport(struct ast_sip_contact_transport *ct);
+
+/*!
+ * \brief Delete a transport for a contact that went away
+ */
+void ast_sip_location_delete_contact_transport(struct ast_sip_contact_transport *ct);
+
+/*!
+ * \brief Retrieve a contact_transport, by URI
+ *
+ * \param contact_uri URI of the contact
+ *
+ * \retval NULL if not found
+ * \retval non-NULL if found
+ */
+struct ast_sip_contact_transport *ast_sip_location_retrieve_contact_transport_by_uri(const char *contact_uri);
+
+/*!
+ * \brief Retrieve a contact_transport, by transport
+ *
+ * \param transport transport the contact uses
+ *
+ * \retval NULL if not found
+ * \retval non-NULL if found
+ */
+struct ast_sip_contact_transport *ast_sip_location_retrieve_contact_transport_by_transport(pjsip_transport *transport);
+
 /*!
  * \brief Add a new contact to an AOR
  *
@@ -1045,7 +1160,7 @@ int ast_sip_append_body(pjsip_tx_data *tdata, const char *body_text);
  * \param src The pj_str_t to copy
  * \param size The size of the destination buffer.
  */
-void ast_copy_pj_str(char *dest, pj_str_t *src, size_t size);
+void ast_copy_pj_str(char *dest, const pj_str_t *src, size_t size);
 
 /*!
  * \brief Get the looked-up endpoint on an out-of dialog request or response
@@ -1085,4 +1200,61 @@ int ast_sip_retrieve_auths(const char *auth_names[], size_t num_auths, struct as
  */
 void ast_sip_cleanup_auths(struct ast_sip_auth *auths[], size_t num_auths);
 
+/*!
+ * \brief Checks if the given content type matches type/subtype.
+ *
+ * Compares the pjsip_media_type with the passed type and subtype and
+ * returns the result of that comparison.  The media type parameters are
+ * ignored.
+ *
+ * \param content_type The pjsip_media_type structure to compare
+ * \param type The media type to compare
+ * \param subtype The media subtype to compare
+ * \retval 0 No match
+ * \retval -1 Match
+ */
+int ast_sip_is_content_type(pjsip_media_type *content_type, char *type, char *subtype);
+
+/*!
+ * \brief Send a security event notification for when an invalid endpoint is requested
+ *
+ * \param name Name of the endpoint requested
+ * \param rdata Received message
+ */
+void ast_sip_report_invalid_endpoint(const char *name, pjsip_rx_data *rdata);
+
+/*!
+ * \brief Send a security event notification for when an ACL check fails
+ *
+ * \param endpoint Pointer to the endpoint in use
+ * \param rdata Received message
+ * \param name Name of the ACL
+ */
+void ast_sip_report_failed_acl(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, const char *name);
+
+/*!
+ * \brief Send a security event notification for when a challenge response has failed
+ *
+ * \param endpoint Pointer to the endpoint in use
+ * \param rdata Received message
+ */
+void ast_sip_report_auth_failed_challenge_response(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata);
+
+/*!
+ * \brief Send a security event notification for when authentication succeeds
+ *
+ * \param endpoint Pointer to the endpoint in use
+ * \param rdata Received message
+ */
+void ast_sip_report_auth_success(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata);
+
+/*!
+ * \brief Send a security event notification for when an authentication challenge is sent
+ *
+ * \param endpoint Pointer to the endpoint in use
+ * \param rdata Received message
+ * \param tdata Sent message
+ */
+void ast_sip_report_auth_challenge_sent(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, pjsip_tx_data *tdata);
+
 #endif /* _RES_SIP_H */
diff --git a/include/asterisk/res_sip_exten_state.h b/include/asterisk/res_sip_exten_state.h
new file mode 100644
index 0000000000..62662f9308
--- /dev/null
+++ b/include/asterisk/res_sip_exten_state.h
@@ -0,0 +1,94 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Kevin Harwell <kharwell@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifndef _RES_SIP_EXTEN_STATE_H
+#define _RES_SIP_EXTEN_STATE_H
+
+#include "asterisk/stringfields.h"
+#include "asterisk/linkedlists.h"
+
+#include "asterisk/pbx.h"
+#include "asterisk/presencestate.h"
+
+
+/*!
+ * \brief Contains information pertaining to extension/device state changes.
+ */
+struct ast_sip_exten_state_data {
+	/*! The extension of the current state change */
+	const char *exten;
+	/*! The extension state of the change */
+	enum ast_extension_states exten_state;
+	/*! The presence state of the change */
+	enum ast_presence_state presence_state;
+	/*! Current device state information */
+	struct ao2_container *device_state_info;
+};
+
+/*!
+ * \brief Extension state provider.
+ */
+struct ast_sip_exten_state_provider {
+	/*! The name of the event this provider registers for */
+	const char *event_name;
+	/*! Type of the body, ex: "application" */
+	const char *type;
+	/*! Subtype of the body, ex: "pidf+xml" */
+	const char *subtype;
+	/*! Type/Subtype together - ex: application/pidf+xml */
+	const char *body_type;
+	/*! Subscription handler to be used and associated with provider */
+	struct ast_sip_subscription_handler *handler;
+
+	/*!
+	 * \brief Create the body text of a NOTIFY request.
+	 *
+	 * Implementors use this to create body information within the given
+	 * ast_str.  That information is then added to the NOTIFY request.
+	 *
+	 * \param data Current extension state changes
+	 * \param local URI of the dialog's local party, e.g. 'from'
+	 * \param remote URI of the dialog's remote party, e.g. 'to'
+	 * \param body_text Out parameter used to populate the NOTIFY msg body
+	 * \retval 0 Successfully created the body's text
+	 * \retval -1 Failed to create the body's text
+	 */
+	int (*create_body)(struct ast_sip_exten_state_data *data, const char *local,
+			   const char *remote, struct ast_str **body_text);
+
+	/*! Next item in the list */
+	AST_LIST_ENTRY(ast_sip_exten_state_provider) next;
+};
+
+/*!
+ * \brief Registers an extension state provider.
+ *
+ * \param obj An extension state provider
+ * \retval 0 Successfully registered the extension state provider
+ * \retval -1 Failed to register the extension state provider
+ */
+int ast_sip_register_exten_state_provider(struct ast_sip_exten_state_provider *obj);
+
+/*!
+ * \brief Unregisters an extension state provider.
+ *
+ * \param obj An extension state provider
+ */
+void ast_sip_unregister_exten_state_provider(struct ast_sip_exten_state_provider *obj);
+
+#endif
diff --git a/include/asterisk/res_sip_pubsub.h b/include/asterisk/res_sip_pubsub.h
index 33614b2853..be443299ca 100644
--- a/include/asterisk/res_sip_pubsub.h
+++ b/include/asterisk/res_sip_pubsub.h
@@ -261,7 +261,22 @@ struct ast_taskprocessor *ast_sip_subscription_get_serializer(struct ast_sip_sub
  * \retval non-NULL The underlying pjsip_evsub
  */
 pjsip_evsub *ast_sip_subscription_get_evsub(struct ast_sip_subscription *sub);
- 
+
+/*!
+ * \brief Get the underlying PJSIP dialog structure
+ *
+ * Call this function when information needs to be retrieved from the
+ * underlying pjsip dialog.
+ *
+ * This function, as well as all methods called on the pjsip_evsub should
+ * be done in a SIP servant thread.
+ *
+ * \param sub The subscription
+ * \retval NULL Failure
+ * \retval non-NULL The underlying pjsip_dialog
+ */
+pjsip_dialog *ast_sip_subscription_get_dlg(struct ast_sip_subscription *sub);
+
 /*!
  * \brief Send a request created via a PJSIP evsub method
  *
diff --git a/include/asterisk/res_sip_session.h b/include/asterisk/res_sip_session.h
index cbed526210..e4b05f7c3e 100644
--- a/include/asterisk/res_sip_session.h
+++ b/include/asterisk/res_sip_session.h
@@ -26,6 +26,8 @@
 #include "asterisk/channel.h"
 /* Needed for ast_sockaddr struct */
 #include "asterisk/netsock.h"
+/* Neeed for ast_sdp_srtp struct */
+#include "asterisk/sdp_srtp.h"
 
 /* Forward declarations */
 struct ast_sip_endpoint;
@@ -41,6 +43,7 @@ struct ast_party_id;
 struct pjmedia_sdp_media;
 struct pjmedia_sdp_session;
 struct ast_rtp_instance;
+struct ast_dsp;
 
 struct ast_sip_session_sdp_handler;
 
@@ -54,6 +57,8 @@ struct ast_sip_session_media {
 	struct ast_sockaddr direct_media_addr;
 	/*! \brief SDP handler that setup the RTP */
 	struct ast_sip_session_sdp_handler *handler;
+	/*! \brief Holds SRTP information */
+	struct ast_sdp_srtp *srtp;
 	/*! \brief Stream is on hold */
 	unsigned int held:1;
 	/*! \brief Stream type this session media handles */
@@ -97,10 +102,18 @@ struct ast_sip_session {
 	pj_timer_entry rescheduled_reinvite;
 	/* Format capabilities pertaining to direct media */
 	struct ast_format_cap *direct_media_cap;
+	/* When we need to forcefully end the session */
+	pj_timer_entry scheduled_termination;
 	/* Identity of endpoint this session deals with */
 	struct ast_party_id id;
 	/* Requested capabilities */
 	struct ast_format_cap *req_caps;
+	/* Codecs overriden by dialplan on an outgoing request */
+	struct ast_codec_pref override_prefs;
+	/* Optional DSP, used only for inband DTMF detection if configured */
+	struct ast_dsp *dsp;
+	/* Whether the termination of the session should be deferred */
+	unsigned int defer_terminate:1;
 };
 
 typedef int (*ast_sip_session_request_creation_cb)(struct ast_sip_session *session, pjsip_tx_data *tdata);
@@ -288,6 +301,13 @@ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint,
  */
 struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint *endpoint, const char *location, const char *request_user, struct ast_format_cap *req_caps);
 
+/*!
+ * \brief Defer local termination of a session until remote side terminates, or an amount of time passes
+ *
+ * \param session The session to defer termination on
+ */
+void ast_sip_session_defer_termination(struct ast_sip_session *session);
+
 /*!
  * \brief Register an SDP handler
  *
@@ -451,6 +471,14 @@ void ast_sip_session_send_response(struct ast_sip_session *session, pjsip_tx_dat
  */
 void ast_sip_session_send_request(struct ast_sip_session *session, pjsip_tx_data *tdata);
 
+/*!
+ * \brief Creates an INVITE request.
+ *
+ * \param session Starting session for the INVITE
+ * \param tdata The created request.
+ */
+int ast_sip_session_create_invite(struct ast_sip_session *session, pjsip_tx_data **tdata);
+
 /*!
  * \brief Send a SIP request and get called back when a response is received
  *
@@ -465,4 +493,18 @@ void ast_sip_session_send_request(struct ast_sip_session *session, pjsip_tx_data
 void ast_sip_session_send_request_with_cb(struct ast_sip_session *session, pjsip_tx_data *tdata,
 		ast_sip_session_response_cb on_response);
 
+/*!
+ * \brief Retrieves a session from a dialog
+ *
+ * \param dlg The dialog to retrieve the session from
+ *
+ * \retval non-NULL if session exists
+ * \retval NULL if no session
+ *
+ * \note The reference count of the session is increased when returned
+ *
+ * \note This function *must* be called with the dialog locked
+ */
+struct ast_sip_session *ast_sip_dialog_get_session(pjsip_dialog *dlg);
+
 #endif /* _RES_SIP_SESSION_H */
diff --git a/include/asterisk/sdp_srtp.h b/include/asterisk/sdp_srtp.h
new file mode 100644
index 0000000000..9b92e0e3f0
--- /dev/null
+++ b/include/asterisk/sdp_srtp.h
@@ -0,0 +1,125 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2006 - 2007, Mikael Magnusson
+ *
+ * Mikael Magnusson <mikma@users.sourceforge.net>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file sdp_srtp.h
+ *
+ * \brief SRTP and SDP Security descriptions
+ *
+ * Specified in RFC 4568
+ * Specified in RFC 3711
+ *
+ * \author Mikael Magnusson <mikma@users.sourceforge.net>
+ */
+
+#ifndef _SDP_SRTP_H
+#define _SDP_SRTP_H
+
+#include <asterisk/rtp_engine.h>
+
+struct ast_sdp_crypto;
+
+/*! \brief structure for secure RTP audio */
+struct ast_sdp_srtp {
+	unsigned int flags;
+	struct ast_sdp_crypto *crypto;
+};
+
+/* SRTP flags */
+#define AST_SRTP_CRYPTO_OFFER_OK	(1 << 1)
+#define AST_SRTP_CRYPTO_TAG_32		(1 << 2)
+#define AST_SRTP_CRYPTO_TAG_80		(1 << 3)
+
+/*!
+ * \brief allocate a ast_sdp_srtp structure
+ * \retval a new malloc'd ast_sdp_srtp structure on success
+ * \retval NULL on failure
+*/
+struct ast_sdp_srtp *ast_sdp_srtp_alloc(void);
+
+/*!
+ * \brief free a ast_sdp_srtp structure
+ * \param srtp a ast_sdp_srtp structure
+*/
+void ast_sdp_srtp_destroy(struct ast_sdp_srtp *srtp);
+
+/*! \brief Initialize an return an ast_sdp_crypto struct
+ *
+ * \details
+ * This function allocates a new ast_sdp_crypto struct and initializes its values
+ *
+ * \retval NULL on failure
+ * \retval a pointer to a  new ast_sdp_crypto structure
+ */
+struct ast_sdp_crypto *ast_sdp_crypto_alloc(void);
+
+/*! \brief Destroy a previously allocated ast_sdp_crypto struct */
+void ast_sdp_crypto_destroy(struct ast_sdp_crypto *crypto);
+
+/*! \brief Parse the a=crypto line from SDP and set appropriate values on the
+ * ast_sdp_crypto struct.
+ *
+ * The attribute line should already have "a=crypto:" removed.
+ *
+ * \param p A valid ast_sdp_crypto struct
+ * \param attr the a:crypto line from SDP
+ * \param rtp The rtp instance associated with the SDP being parsed
+ * \param srtp SRTP structure
+ *
+ * \retval 0 success
+ * \retval nonzero failure
+ */
+int ast_sdp_crypto_process(struct ast_rtp_instance *rtp, struct ast_sdp_srtp *srtp, const char *attr);
+
+/*! \brief Generate an SRTP a=crypto offer
+ *
+ * \details
+ * The offer is stored on the ast_sdp_crypto struct in a_crypto
+ *
+ * \param p A valid ast_sdp_crypto struct
+ * \param taglen Length
+ *
+ * \retval 0 success
+ * \retval nonzero failure
+ */
+int ast_sdp_crypto_build_offer(struct ast_sdp_crypto *p, int taglen);
+
+
+/*! \brief Get the crypto attribute line for the srtp structure
+ *
+ * The attribute line does not contain the initial "a=crypto:" and does
+ * not terminate with "\r\n".
+ *
+ * \param srtp The ast_sdp_srtp structure for which to get an attribute line
+ * \param dtls_enabled Whether this connection is encrypted with datagram TLS
+ * \param default_taglen_32 Whether to default to a tag length of 32 instead of 80
+ *
+ * \retval An attribute line containing cryptographic information
+ * \retval NULL if the srtp structure does not require an attribute line containing crypto information
+ */
+const char *ast_sdp_srtp_get_attrib(struct ast_sdp_srtp *srtp, int dtls_enabled, int default_taglen_32);
+
+/*! \brief Get the RTP profile in use by a media session
+ *
+ * \param sdes_active Whether the media session is using SDES-SRTP
+ * \param instance The RTP instance associated with this media session
+ * \param using_avpf Whether the media session is using early feedback (AVPF)
+ *
+ * \retval A non-allocated string describing the profile in use (does not need to be freed)
+ */
+char *ast_sdp_get_rtp_profile(unsigned int sdes_active, struct ast_rtp_instance *instance, unsigned int using_avpf);
+#endif	/* _SDP_CRYPTO_H */
diff --git a/main/pbx.c b/main/pbx.c
index af988dcf96..db737327f7 100644
--- a/main/pbx.c
+++ b/main/pbx.c
@@ -11814,7 +11814,7 @@ int ast_pbx_init(void)
 	hintdevices = ao2_container_alloc(HASH_EXTENHINT_SIZE, hintdevice_hash_cb, hintdevice_cmp_multiple);
 	statecbs = ao2_container_alloc(1, NULL, statecbs_cmp);
 
-	ast_register_atexit(pbx_shutdown);
+	ast_register_cleanup(pbx_shutdown);
 
 	return (hints && hintdevices && statecbs) ? 0 : -1;
 }
diff --git a/channels/sip/sdp_crypto.c b/main/sdp_srtp.c
similarity index 68%
rename from channels/sip/sdp_crypto.c
rename to main/sdp_srtp.c
index c27e882c27..85dc108a60 100644
--- a/channels/sip/sdp_crypto.c
+++ b/main/sdp_srtp.c
@@ -16,10 +16,11 @@
  * at the top of the source tree.
  */
 
-/*! \file sdp_crypto.c
+/*! \file ast_sdp_crypto.c
  *
- * \brief SDP Security descriptions
+ * \brief SRTP and SDP Security descriptions
  *
+ * Specified in RFC 3711
  * Specified in RFC 4568
  *
  * \author Mikael Magnusson <mikma@users.sourceforge.net>
@@ -35,8 +36,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 #include "asterisk/options.h"
 #include "asterisk/utils.h"
-#include "include/sdp_crypto.h"
-#include "include/srtp.h"
+#include "asterisk/sdp_srtp.h"
 
 #define SRTP_MASTER_LEN 30
 #define SRTP_MASTERKEY_LEN 16
@@ -46,7 +46,26 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 extern struct ast_srtp_res *res_srtp;
 extern struct ast_srtp_policy_res *res_srtp_policy;
 
-struct sdp_crypto {
+struct ast_sdp_srtp *ast_sdp_srtp_alloc(void)
+{
+	if (!ast_rtp_engine_srtp_is_registered()) {
+	       ast_debug(1, "No SRTP module loaded, can't setup SRTP session.\n");
+	       return NULL;
+	}
+
+	return ast_calloc(1, sizeof(struct ast_sdp_srtp));
+}
+
+void ast_sdp_srtp_destroy(struct ast_sdp_srtp *srtp)
+{
+	if (srtp->crypto) {
+		ast_sdp_crypto_destroy(srtp->crypto);
+	}
+	srtp->crypto = NULL;
+	ast_free(srtp);
+}
+
+struct ast_sdp_crypto {
 	char *a_crypto;
 	unsigned char local_key[SRTP_MASTER_LEN];
 	char *tag;
@@ -56,12 +75,7 @@ struct sdp_crypto {
 
 static int set_crypto_policy(struct ast_srtp_policy *policy, int suite_val, const unsigned char *master_key, unsigned long ssrc, int inbound);
 
-static struct sdp_crypto *sdp_crypto_alloc(void)
-{
-	return ast_calloc(1, sizeof(struct sdp_crypto));
-}
-
-void sdp_crypto_destroy(struct sdp_crypto *crypto)
+void ast_sdp_crypto_destroy(struct ast_sdp_crypto *crypto)
 {
 	ast_free(crypto->a_crypto);
 	crypto->a_crypto = NULL;
@@ -70,9 +84,9 @@ void sdp_crypto_destroy(struct sdp_crypto *crypto)
 	ast_free(crypto);
 }
 
-struct sdp_crypto *sdp_crypto_setup(void)
+struct ast_sdp_crypto *ast_sdp_crypto_alloc(void)
 {
-	struct sdp_crypto *p;
+	struct ast_sdp_crypto *p;
 	int key_len;
 	unsigned char remote_key[SRTP_MASTER_LEN];
 
@@ -80,12 +94,12 @@ struct sdp_crypto *sdp_crypto_setup(void)
 		return NULL;
 	}
 
-	if (!(p = sdp_crypto_alloc())) {
+	if (!(p = ast_calloc(1, sizeof(*p)))) {
 		return NULL;
 	}
 
 	if (res_srtp->get_random(p->local_key, sizeof(p->local_key)) < 0) {
-		sdp_crypto_destroy(p);
+		ast_sdp_crypto_destroy(p);
 		return NULL;
 	}
 
@@ -133,7 +147,7 @@ static int set_crypto_policy(struct ast_srtp_policy *policy, int suite_val, cons
 	return 0;
 }
 
-static int sdp_crypto_activate(struct sdp_crypto *p, int suite_val, unsigned char *remote_key, struct ast_rtp_instance *rtp)
+static int crypto_activate(struct ast_sdp_crypto *p, int suite_val, unsigned char *remote_key, struct ast_rtp_instance *rtp)
 {
 	struct ast_srtp_policy *local_policy = NULL;
 	struct ast_srtp_policy *remote_policy = NULL;
@@ -189,7 +203,7 @@ err:
 	return res;
 }
 
-int sdp_crypto_process(struct sdp_crypto *p, const char *attr, struct ast_rtp_instance *rtp, struct sip_srtp *srtp)
+int ast_sdp_crypto_process(struct ast_rtp_instance *rtp, struct ast_sdp_srtp *srtp, const char *attr)
 {
 	char *str = NULL;
 	char *tag = NULL;
@@ -204,6 +218,7 @@ int sdp_crypto_process(struct sdp_crypto *p, const char *attr, struct ast_rtp_in
 	int suite_val = 0;
 	unsigned char remote_key[SRTP_MASTER_LEN];
 	int taglen = 0;
+	struct ast_sdp_crypto *crypto = srtp->crypto;
 
 	if (!ast_rtp_engine_srtp_is_registered()) {
 		return -1;
@@ -211,7 +226,6 @@ int sdp_crypto_process(struct sdp_crypto *p, const char *attr, struct ast_rtp_in
 
 	str = ast_strdupa(attr);
 
-	strsep(&str, ":");
 	tag = strsep(&str, " ");
 	suite = strsep(&str, " ");
 	key_params = strsep(&str, " ");
@@ -229,11 +243,11 @@ int sdp_crypto_process(struct sdp_crypto *p, const char *attr, struct ast_rtp_in
 
 	if (!strcmp(suite, "AES_CM_128_HMAC_SHA1_80")) {
 		suite_val = AST_AES_CM_128_HMAC_SHA1_80;
-		ast_set_flag(srtp, SRTP_CRYPTO_TAG_80);
+		ast_set_flag(srtp, AST_SRTP_CRYPTO_TAG_80);
 		taglen = 80;
 	} else if (!strcmp(suite, "AES_CM_128_HMAC_SHA1_32")) {
 		suite_val = AST_AES_CM_128_HMAC_SHA1_32;
-		ast_set_flag(srtp, SRTP_CRYPTO_TAG_32);
+		ast_set_flag(srtp, AST_SRTP_CRYPTO_TAG_32);
 		taglen = 32;
 	} else {
 		ast_log(LOG_WARNING, "Unsupported crypto suite: %s\n", suite);
@@ -271,48 +285,98 @@ int sdp_crypto_process(struct sdp_crypto *p, const char *attr, struct ast_rtp_in
 		return -1;
 	}
 
-	if (!memcmp(p->remote_key, remote_key, sizeof(p->remote_key))) {
+	if (!memcmp(crypto->remote_key, remote_key, sizeof(crypto->remote_key))) {
 		ast_debug(1, "SRTP remote key unchanged; maintaining current policy\n");
+		ast_set_flag(srtp, AST_SRTP_CRYPTO_OFFER_OK);
 		return 0;
 	}
-	memcpy(p->remote_key, remote_key, sizeof(p->remote_key));
+	memcpy(crypto->remote_key, remote_key, sizeof(crypto->remote_key));
 
-	if (sdp_crypto_activate(p, suite_val, remote_key, rtp) < 0) {
+	if (crypto_activate(crypto, suite_val, remote_key, rtp) < 0) {
 		return -1;
 	}
 
-	if (!p->tag) {
-		ast_log(LOG_DEBUG, "Accepting crypto tag %s\n", tag);
-		p->tag = ast_strdup(tag);
-		if (!p->tag) {
+	if (!crypto->tag) {
+		ast_debug(1, "Accepting crypto tag %s\n", tag);
+		crypto->tag = ast_strdup(tag);
+		if (!crypto->tag) {
 			ast_log(LOG_ERROR, "Could not allocate memory for tag\n");
 			return -1;
 		}
 	}
 
 	/* Finally, rebuild the crypto line */
-	return sdp_crypto_offer(p, taglen);
+	if (ast_sdp_crypto_build_offer(crypto, taglen)) {
+		return -1;
+	}
+
+	ast_set_flag(srtp, AST_SRTP_CRYPTO_OFFER_OK);
+	return 0;
 }
 
-int sdp_crypto_offer(struct sdp_crypto *p, int taglen)
+int ast_sdp_crypto_build_offer(struct ast_sdp_crypto *p, int taglen)
 {
 	/* Rebuild the crypto line */
 	if (p->a_crypto) {
 		ast_free(p->a_crypto);
 	}
 
-	if (ast_asprintf(&p->a_crypto, "a=crypto:%s AES_CM_128_HMAC_SHA1_%i inline:%s\r\n",
+	if (ast_asprintf(&p->a_crypto, "%s AES_CM_128_HMAC_SHA1_%i inline:%s",
 			 p->tag ? p->tag : "1", taglen, p->local_key64) == -1) {
 			ast_log(LOG_ERROR, "Could not allocate memory for crypto line\n");
 		return -1;
 	}
 
-	ast_log(LOG_DEBUG, "Crypto line: %s", p->a_crypto);
+	ast_debug(1, "Crypto line: a=crypto:%s\n", p->a_crypto);
 
 	return 0;
 }
 
-const char *sdp_crypto_attrib(struct sdp_crypto *p)
+const char *ast_sdp_srtp_get_attrib(struct ast_sdp_srtp *srtp, int dtls_enabled, int default_taglen_32)
+{
+	int taglen = default_taglen_32 ? 32 : 80;
+
+	if (!srtp) {
+		return NULL;
+	}
+
+	/* Set encryption properties */
+	if (!srtp->crypto) {
+		srtp->crypto = ast_sdp_crypto_alloc();
+	}
+
+	if (dtls_enabled) {
+		/* If DTLS-SRTP is enabled the key details will be pulled from TLS */
+		return NULL;
+	}
+
+	/* set the key length based on INVITE or settings */
+	if (ast_test_flag(srtp, AST_SRTP_CRYPTO_TAG_80)) {
+		taglen = 80;
+	} else if (ast_test_flag(srtp, AST_SRTP_CRYPTO_TAG_32)) {
+		taglen = 32;
+	}
+
+	if (srtp->crypto && (ast_sdp_crypto_build_offer(srtp->crypto, taglen) >= 0)) {
+		return srtp->crypto->a_crypto;
+	}
+
+	ast_log(LOG_WARNING, "No SRTP key management enabled\n");
+	return NULL;
+}
+
+char *ast_sdp_get_rtp_profile(unsigned int sdes_active, struct ast_rtp_instance *instance, unsigned int using_avpf)
 {
-	return p->a_crypto;
+	struct ast_rtp_engine_dtls *dtls;
+
+	if ((dtls = ast_rtp_instance_get_dtls(instance)) && dtls->active(instance)) {
+		return using_avpf ? "UDP/TLS/RTP/SAVPF" : "UDP/TLS/RTP/SAVP";
+	} else {
+		if (using_avpf) {
+			return sdes_active ? "RTP/SAVPF" : "RTP/AVPF";
+		} else {
+			return sdes_active ? "RTP/SAVP" : "RTP/AVP";
+		}
+	}
 }
+
diff --git a/res/res_sip.c b/res/res_sip.c
index 1af3fb78e5..ebbf596de5 100644
--- a/res/res_sip.c
+++ b/res/res_sip.c
@@ -53,7 +53,7 @@
 					It contains the core SIP related options only, endpoints are <emphasis>NOT</emphasis>
 					dialable entries of their own. Communication with another SIP device is
 					accomplished via Addresses of Record (AoRs) which have one or more
-					contacts assicated with them. Endpoints <emphasis>NOT</emphasis> configured to 
+					contacts assicated with them. Endpoints <emphasis>NOT</emphasis> configured to
 					use a <literal>transport</literal> will default to first transport found
 					in <filename>res_sip.conf</filename> that matches its type.
 					</para>
@@ -62,6 +62,12 @@
 					first transport that matches the type. A SIP URI of <literal>sip:5000@[11::33]</literal>
 					will use the first IPv6 transport and try to send the request.
 					</para>
+					<para>If the anonymous endpoint identifier is in use an endpoint with the name
+					"anonymous@domain" will be searched for as a last resort. If this is not found
+					it will fall back to searching for "anonymous". If neither endpoints are found
+					the anonymous endpoint identifier will not return an endpoint and anonymous
+					calling will not be possible.
+					</para>
 				</description>
 				<configOption name="100rel" default="yes">
 					<synopsis>Allow support for RFC3262 provisional ACK tags</synopsis>
@@ -161,6 +167,19 @@
 						</enumlist>
 					</description>
 				</configOption>
+				<configOption name="connected_line_method" default="invite">
+					<synopsis>Connected line method type</synopsis>
+					<description>
+						<para>Method used when updating connected line information.</para>
+						<enumlist>
+							<enum name="invite" />
+							<enum name="reinvite">
+								<para>Alias for the <literal>invite</literal> value.</para>
+							</enum>
+							<enum name="update" />
+						</enumlist>
+					</description>
+				</configOption>
 				<configOption name="direct_media" default="yes">
 					<synopsis>Determines whether media may flow directly between endpoints.</synopsis>
 				</configOption>
@@ -223,13 +242,6 @@
 				<configOption name="outbound_proxy">
 					<synopsis>Proxy through which to send requests</synopsis>
 				</configOption>
-				<configOption name="qualify_frequency" default="0">
-					<synopsis>Interval at which to qualify an endpoint</synopsis>
-					<description><para>
-						Interval between attempts to qualify the endpoint for reachability.
-						If <literal>0</literal> never qualify. Time in seconds.
-					</para></description>
-				</configOption>
 				<configOption name="rewrite_contact">
 					<synopsis>Allow Contact header to be rewritten with the source IP address-port</synopsis>
 				</configOption>
@@ -298,6 +310,75 @@
 				<configOption name="use_ptime" default="no">
 					<synopsis>Use Endpoint's requested packetisation interval</synopsis>
 				</configOption>
+				<configOption name="use_avpf" default="no">
+					<synopsis>Determines whether res_sip will use and enforce usage of AVPF for this
+					endpoint.</synopsis>
+					<description><para>
+						If set to <literal>yes</literal>, res_sip will use use the AVPF or SAVPF RTP
+						profile for all media offers on outbound calls and media updates and will
+						decline media offers not using the AVPF or SAVPF profile.
+					</para><para>
+						If set to <literal>no</literal>, res_sip will use use the AVP or SAVP RTP
+						profile for all media offers on outbound calls and media updates and will
+						decline media offers not using the AVP or SAVP profile.
+					</para></description>
+				</configOption>
+				<configOption name="media_encryption" default="no">
+					<synopsis>Determines whether res_sip will use and enforce usage of media encryption
+					for this endpoint.</synopsis>
+					<description>
+						<enumlist>
+							<enum name="no"><para>
+								res_sip will offer no encryption and allow no encryption to be setup.
+							</para></enum>
+							<enum name="sdes"><para>
+								res_sip will offer standard SRTP setup via in-SDP keys. Encrypted SIP
+								transport should be used in conjunction with this option to prevent
+								exposure of media encryption keys.
+							</para></enum>
+						</enumlist>
+					</description>
+				</configOption>
+				<configOption name="inband_progress" default="no">
+					<synopsis>Determines whether chan_gulp will indicate ringing using inband
+					    progress.</synopsis>
+					<description><para>
+						If set to <literal>yes</literal>, chan_gulp will send a 183 Session Progress
+						when told to indicate ringing and will immediately start sending ringing
+						as audio.
+					</para><para>
+						If set to <literal>no</literal>, chan_gulp will send a 180 Ringing when told
+						to indicate ringing and will NOT send it as audio.
+					</para></description>
+				</configOption>
+				<configOption name="callgroup">
+					<synopsis>The numeric pickup groups for a channel.</synopsis>
+					<description><para>
+						Can be set to a comma separated list of numbers or ranges between the values
+						of 0-63 (maximum of 64 groups).
+					</para></description>
+				</configOption>
+				<configOption name="pickupgroup">
+					<synopsis>The numeric pickup groups that a channel can pickup.</synopsis>
+					<description><para>
+						Can be set to a comma separated list of numbers or ranges between the values
+						of 0-63 (maximum of 64 groups).
+					</para></description>
+				</configOption>
+				<configOption name="namedcallgroup">
+					<synopsis>The named pickup groups for a channel.</synopsis>
+					<description><para>
+						Can be set to a comma separated list of case sensitive strings limited by
+						supported line length.
+					</para></description>
+				</configOption>
+				<configOption name="namedpickupgroup">
+					<synopsis>The named pickup groups that a channel can pickup.</synopsis>
+					<description><para>
+						Can be set to a comma separated list of case sensitive strings limited by
+						supported line length.
+					</para></description>
+				</configOption>
 				<configOption name="devicestate_busy_at" default="0">
 					<synopsis>The number of in-use channels which will cause busy to be returned as device state</synopsis>
 					<description><para>
@@ -479,6 +560,35 @@
 						Time to keep alive a contact. String style specification.
 					</para></description>
 				</configOption>
+				<configOption name="qualify_frequency" default="0">
+					<synopsis>Interval at which to qualify a contact</synopsis>
+					<description><para>
+						Interval between attempts to qualify the contact for reachability.
+						If <literal>0</literal> never qualify. Time in seconds.
+					</para></description>
+				</configOption>
+			</configObject>
+			<configObject name="contact_status">
+				<synopsis>Status for a contact</synopsis>
+				<description><para>
+					The contact status keeps track of whether or not a contact is reachable
+					and how long it took to qualify the contact (round trip time).
+				</para></description>
+				<configOption name="status">
+					<synopsis>A contact's status</synopsis>
+					<description>
+						<enumlist>
+							<enum name="AVAILABLE" />
+							<enum name="UNAVAILABLE" />
+						</enumlist>
+					</description>
+				</configOption>
+				<configOption name="rtt">
+					<synopsis>Round trip time</synopsis>
+					<description><para>
+						The time, in microseconds, it took to qualify the contact.
+					</para></description>
+				</configOption>
 			</configObject>
 			<configObject name="aor">
 				<synopsis>The configuration for a location of an endpoint</synopsis>
@@ -549,6 +659,20 @@
 				<configOption name="type">
 					<synopsis>Must be of type 'aor'.</synopsis>
 				</configOption>
+				<configOption name="qualify_frequency" default="0">
+					<synopsis>Interval at which to qualify an AoR</synopsis>
+					<description><para>
+						Interval between attempts to qualify the AoR for reachability.
+						If <literal>0</literal> never qualify. Time in seconds.
+					</para></description>
+				</configOption>
+				<configOption name="authenticate_qualify" default="no">
+					<synopsis>Authenticates a qualify request if needed</synopsis>
+					<description><para>
+						If true and a qualify request receives a challenge or authenticate response
+						authentication is attempted before declaring the contact available.
+					</para></description>
+				</configOption>
 			</configObject>
 		</configFile>
 	</configInfo>
@@ -782,7 +906,7 @@ static int sip_dialog_create_from(pj_pool_t *pool, pj_str_t *from, const char *u
 	}
 
 	/* If the host is IPv6 turn the transport into an IPv6 version */
-	if (pj_strchr(&sip_uri->host, ':')) {
+	if (pj_strchr(&sip_uri->host, ':') && type < PJSIP_TRANSPORT_START_OTHER) {
 		type = (pjsip_transport_type_e)(((int)type) + PJSIP_TRANSPORT_IPV6);
 	}
 
@@ -792,8 +916,8 @@ static int sip_dialog_create_from(pj_pool_t *pool, pj_str_t *from, const char *u
 		return -1;
 	}
 
-	/* If IPv6 was not specified in the host but is in the transport, set the proper type */
-	if (!pj_strchr(&sip_uri->host, ':') && pj_strchr(&local_addr, ':')) {
+	/* If IPv6 was specified in the transport, set the proper type */
+	if (pj_strchr(&local_addr, ':') && type < PJSIP_TRANSPORT_START_OTHER) {
 		type = (pjsip_transport_type_e)(((int)type) + PJSIP_TRANSPORT_IPV6);
 	}
 
@@ -828,10 +952,10 @@ static int sip_get_tpselector_from_endpoint(const struct ast_sip_endpoint *endpo
 		return -1;
 	}
 
-	if (transport->type == AST_TRANSPORT_UDP) {
+	if (transport->state->transport) {
 		selector->type = PJSIP_TPSELECTOR_TRANSPORT;
 		selector->u.transport = transport->state->transport;
-	} else if (transport->type == AST_TRANSPORT_TCP || transport->type == AST_TRANSPORT_TLS) {
+	} else if (transport->state->factory) {
 		selector->type = PJSIP_TPSELECTOR_LISTENER;
 		selector->u.listener = transport->state->factory;
 	} else {
@@ -841,6 +965,22 @@ static int sip_get_tpselector_from_endpoint(const struct ast_sip_endpoint *endpo
 	return 0;
 }
 
+static int sip_get_tpselector_from_uri(const char *uri, pjsip_tpselector *selector)
+{
+	RAII_VAR(struct ast_sip_contact_transport *, contact_transport, NULL, ao2_cleanup);
+
+	contact_transport = ast_sip_location_retrieve_contact_transport_by_uri(uri);
+
+	if (!contact_transport) {
+		return -1;
+	}
+
+	selector->type = PJSIP_TPSELECTOR_TRANSPORT;
+	selector->u.transport = contact_transport->transport;
+
+	return 0;
+}
+
 pjsip_dialog *ast_sip_create_dialog(const struct ast_sip_endpoint *endpoint, const char *uri, const char *request_user)
 {
 	pj_str_t local_uri = { "sip:temp@temp", 13 }, remote_uri;
@@ -855,7 +995,7 @@ pjsip_dialog *ast_sip_create_dialog(const struct ast_sip_endpoint *endpoint, con
 		return NULL;
 	}
 
-	if (sip_get_tpselector_from_endpoint(endpoint, &selector)) {
+	if (sip_get_tpselector_from_uri(uri, &selector) && sip_get_tpselector_from_endpoint(endpoint, &selector)) {
 		pjsip_dlg_terminate(dlg);
 		return NULL;
 	}
@@ -905,6 +1045,7 @@ pjsip_dialog *ast_sip_create_dialog(const struct ast_sip_endpoint *endpoint, con
 
 /* PJSIP doesn't know about the INFO method, so we have to define it ourselves */
 const pjsip_method pjsip_info_method = {PJSIP_OTHER_METHOD, {"INFO", 4} };
+const pjsip_method pjsip_message_method = {PJSIP_OTHER_METHOD, {"MESSAGE", 7} };
 
 static struct {
 	const char *method;
@@ -920,6 +1061,7 @@ static struct {
 	{ "NOTIFY", &pjsip_notify_method },
 	{ "PUBLISH", &pjsip_publish_method },
 	{ "INFO", &pjsip_info_method },
+	{ "MESSAGE", &pjsip_message_method },
 };
 
 static const pjsip_method *get_pjsip_method(const char *method)
@@ -953,6 +1095,11 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s
 	pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
 
 	if (ast_strlen_zero(uri)) {
+		if (!endpoint) {
+			ast_log(LOG_ERROR, "An endpoint and/or uri must be specified\n");
+			return -1;
+		}
+
 		contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
 		if (!contact || ast_strlen_zero(contact->uri)) {
 			ast_log(LOG_ERROR, "Unable to retrieve contact for endpoint %s\n",
@@ -965,10 +1112,12 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s
 		pj_cstr(&remote_uri, uri);
 	}
 
-	if (sip_get_tpselector_from_endpoint(endpoint, &selector)) {
-		ast_log(LOG_ERROR, "Unable to retrieve PJSIP transport selector for endpoint %s\n",
+	if (endpoint) {
+		if (sip_get_tpselector_from_endpoint(endpoint, &selector)) {
+			ast_log(LOG_ERROR, "Unable to retrieve PJSIP transport selector for endpoint %s\n",
 				ast_sorcery_object_get_id(endpoint));
-		return -1;
+			return -1;
+		}
 	}
 
 	pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Outbound request", 256, 256);
@@ -1073,7 +1222,7 @@ int ast_sip_add_header(pjsip_tx_data *tdata, const char *name, const char *value
 	pj_str_t hdr_name;
 	pj_str_t hdr_value;
 	pjsip_generic_string_hdr *hdr;
-	
+
 	pj_cstr(&hdr_name, name);
 	pj_cstr(&hdr_value, value);
 
@@ -1092,7 +1241,7 @@ static pjsip_msg_body *ast_body_to_pjsip_body(pj_pool_t *pool, const struct ast_
 	pj_cstr(&type, body->type);
 	pj_cstr(&subtype, body->subtype);
 	pj_cstr(&body_text, body->body_text);
-	
+
 	return pjsip_msg_body_create(pool, &type, &subtype, &body_text);
 }
 
@@ -1213,13 +1362,26 @@ int ast_sip_push_task_synchronous(struct ast_taskprocessor *serializer, int (*si
 	return std.fail;
 }
 
-void ast_copy_pj_str(char *dest, pj_str_t *src, size_t size)
+void ast_copy_pj_str(char *dest, const pj_str_t *src, size_t size)
 {
 	size_t chars_to_copy = MIN(size - 1, pj_strlen(src));
 	memcpy(dest, pj_strbuf(src), chars_to_copy);
 	dest[chars_to_copy] = '\0';
 }
 
+int ast_sip_is_content_type(pjsip_media_type *content_type, char *type, char *subtype)
+{
+	pjsip_media_type compare;
+
+	if (!content_type) {
+		return 0;
+	}
+
+	pjsip_media_type_init2(&compare, type, subtype);
+
+	return pjsip_media_type_cmp(content_type, &compare, 0) ? -1 : 0;
+}
+
 pj_caching_pool caching_pool;
 pj_pool_t *memory_pool;
 pj_thread_t *monitor_thread;
@@ -1352,6 +1514,8 @@ static int load_module(void)
 
 	ast_res_sip_init_options_handling(0);
 
+	ast_res_sip_init_contact_transports();
+
 return AST_MODULE_LOAD_SUCCESS;
 
 error:
diff --git a/res/res_sip.exports.in b/res/res_sip.exports.in
index 010f90cb1d..625a02f7ec 100644
--- a/res/res_sip.exports.in
+++ b/res/res_sip.exports.in
@@ -37,6 +37,10 @@
 		LINKER_SYMBOL_PREFIXast_sip_location_retrieve_contact_from_aor_list;
 		LINKER_SYMBOL_PREFIXast_sip_location_retrieve_aor_contacts;
 		LINKER_SYMBOL_PREFIXast_sip_location_retrieve_contact;
+		LINKER_SYMBOL_PREFIXast_sip_location_add_contact_transport;
+		LINKER_SYMBOL_PREFIXast_sip_location_delete_contact_transport;
+		LINKER_SYMBOL_PREFIXast_sip_location_retrieve_contact_transport_by_uri;
+		LINKER_SYMBOL_PREFIXast_sip_location_retrieve_contact_transport_by_transport;
 		LINKER_SYMBOL_PREFIXast_sip_location_add_contact;
 		LINKER_SYMBOL_PREFIXast_sip_location_update_contact;
 		LINKER_SYMBOL_PREFIXast_sip_location_delete_contact;
@@ -47,6 +51,12 @@
 		LINKER_SYMBOL_PREFIXast_sip_dialog_get_endpoint;
 		LINKER_SYMBOL_PREFIXast_sip_retrieve_auths;
 		LINKER_SYMBOL_PREFIXast_sip_cleanup_auths;
+		LINKER_SYMBOL_PREFIXast_sip_is_content_type;
+		LINKER_SYMBOL_PREFIXast_sip_report_invalid_endpoint;
+		LINKER_SYMBOL_PREFIXast_sip_report_failed_acl;
+		LINKER_SYMBOL_PREFIXast_sip_report_auth_failed_challenge_response;
+		LINKER_SYMBOL_PREFIXast_sip_report_auth_success;
+		LINKER_SYMBOL_PREFIXast_sip_report_auth_challenge_sent;
 	local:
 		*;
 };
diff --git a/res/res_sip/config_transport.c b/res/res_sip/config_transport.c
index 0df8c66adf..1d60274b79 100644
--- a/res/res_sip/config_transport.c
+++ b/res/res_sip/config_transport.c
@@ -145,6 +145,8 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj)
 		transport->tls.password = pj_str((char*)transport->password);
 
 		res = pjsip_tls_transport_start2(ast_sip_get_pjsip_endpoint(), &transport->tls, &transport->host, NULL, transport->async_operations, &transport->state->factory);
+	} else if ((transport->type == AST_TRANSPORT_WS) || (transport->type == AST_TRANSPORT_WSS)) {
+		res = PJ_SUCCESS;
 	}
 
 	if (res != PJ_SUCCESS) {
@@ -168,8 +170,11 @@ static int transport_protocol_handler(const struct aco_option *opt, struct ast_v
 		transport->type = AST_TRANSPORT_TCP;
 	} else if (!strcasecmp(var->value, "tls")) {
 		transport->type = AST_TRANSPORT_TLS;
+	} else if (!strcasecmp(var->value, "ws")) {
+		transport->type = AST_TRANSPORT_WS;
+	} else if (!strcasecmp(var->value, "wss")) {
+		transport->type = AST_TRANSPORT_WSS;
 	} else {
-		/* TODO: Implement websockets */
 		return -1;
 	}
 
diff --git a/res/res_sip/include/res_sip_private.h b/res/res_sip/include/res_sip_private.h
index 318510aae0..3625bab313 100644
--- a/res/res_sip/include/res_sip_private.h
+++ b/res/res_sip/include/res_sip_private.h
@@ -39,6 +39,14 @@ int ast_res_sip_reload_configuration(void);
  */
 int ast_res_sip_init_options_handling(int reload);
 
+/*!
+ * \brief Initialize transport storage for contacts.
+ *
+ * \retval 0 on success
+ * \retval other on failure
+ */
+int ast_res_sip_init_contact_transports(void);
+
 /*!
  * \brief Initialize outbound authentication support
  *
diff --git a/res/res_sip/location.c b/res/res_sip/location.c
index 91521c8135..d0b0a28c95 100644
--- a/res/res_sip/location.c
+++ b/res/res_sip/location.c
@@ -24,6 +24,10 @@
 #include "asterisk/logger.h"
 #include "asterisk/astobj2.h"
 #include "asterisk/sorcery.h"
+#include "include/res_sip_private.h"
+
+#define CONTACT_TRANSPORTS_BUCKETS 7
+static struct ao2_container *contact_transports;
 
 /*! \brief Destructor for AOR */
 static void aor_destroy(void *obj)
@@ -70,6 +74,48 @@ static void *contact_alloc(const char *name)
 	return contact;
 }
 
+/*! \brief Callback function for finding a contact_transport by URI */
+static int contact_transport_find_by_uri(void *obj, void *arg, int flags)
+{
+	struct ast_sip_contact_transport *ct = obj;
+	const char *contact_uri = arg;
+
+	return (!strcmp(ct->uri, contact_uri)) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+/*! \brief Callback function for finding a contact_transport by transport */
+static int contact_transport_find_by_transport(void *obj, void *arg, int flags)
+{
+	struct ast_sip_contact_transport *ct = obj;
+	pjsip_transport *transport = arg;
+
+	return (ct->transport == transport) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+void ast_sip_location_add_contact_transport(struct ast_sip_contact_transport *ct)
+{
+	ao2_link(contact_transports, ct);
+
+	return;
+}
+
+void ast_sip_location_delete_contact_transport(struct ast_sip_contact_transport *ct)
+{
+	ao2_unlink(contact_transports, ct);
+
+	return;
+}
+
+struct ast_sip_contact_transport *ast_sip_location_retrieve_contact_transport_by_uri(const char *contact_uri)
+{
+	return ao2_callback(contact_transports, 0, contact_transport_find_by_uri, (void *)contact_uri);
+}
+
+struct ast_sip_contact_transport *ast_sip_location_retrieve_contact_transport_by_transport(pjsip_transport *transport)
+{
+	return ao2_callback(contact_transports, 0, contact_transport_find_by_transport, transport);
+}
+
 struct ast_sip_aor *ast_sip_location_retrieve_aor(const char *aor_name)
 {
 	return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "aor", aor_name);
@@ -189,6 +235,8 @@ int ast_sip_location_add_contact(struct ast_sip_aor *aor, const char *uri, struc
 
 	ast_string_field_set(contact, uri, uri);
 	contact->expiration_time = expiration_time;
+	contact->qualify_frequency = aor->qualify_frequency;
+	contact->authenticate_qualify = aor->authenticate_qualify;
 
 	return ast_sorcery_create(ast_sip_get_sorcery(), contact);
 }
@@ -248,11 +296,15 @@ int ast_sip_initialize_sorcery_location(struct ast_sorcery *sorcery)
 	ast_sorcery_object_field_register(sorcery, "contact", "type", "", OPT_NOOP_T, 0, 0);
 	ast_sorcery_object_field_register(sorcery, "contact", "uri", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, uri));
 	ast_sorcery_object_field_register_custom(sorcery, "contact", "expiration_time", "", expiration_str2struct, expiration_struct2str, 0, 0);
+	ast_sorcery_object_field_register(sorcery, "contact", "qualify_frequency", 0, OPT_UINT_T,
+					  PARSE_IN_RANGE, FLDSET(struct ast_sip_contact, qualify_frequency), 0, 86400);
 
 	ast_sorcery_object_field_register(sorcery, "aor", "type", "", OPT_NOOP_T, 0, 0);
 	ast_sorcery_object_field_register(sorcery, "aor", "minimum_expiration", "60", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, minimum_expiration));
 	ast_sorcery_object_field_register(sorcery, "aor", "maximum_expiration", "7200", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, maximum_expiration));
 	ast_sorcery_object_field_register(sorcery, "aor", "default_expiration", "3600", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, default_expiration));
+	ast_sorcery_object_field_register(sorcery, "aor", "qualify_frequency", 0, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_aor, qualify_frequency), 0, 86400);
+	ast_sorcery_object_field_register(sorcery, "aor", "authenticate_qualify", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, authenticate_qualify));
 	ast_sorcery_object_field_register(sorcery, "aor", "max_contacts", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, max_contacts));
 	ast_sorcery_object_field_register(sorcery, "aor", "remove_existing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, remove_existing));
 	ast_sorcery_object_field_register_custom(sorcery, "aor", "contact", "", permanent_uri_handler, NULL, 0, 0);
@@ -260,3 +312,17 @@ int ast_sip_initialize_sorcery_location(struct ast_sorcery *sorcery)
 
 	return 0;
 }
+
+int ast_res_sip_init_contact_transports(void)
+{
+	if (contact_transports) {
+		ao2_t_ref(contact_transports, -1, "Remove old contact transports");
+	}
+
+	contact_transports = ao2_t_container_alloc_options(AO2_ALLOC_OPT_LOCK_RWLOCK, CONTACT_TRANSPORTS_BUCKETS, NULL, NULL, "Create container for contact transports");
+	if (!contact_transports) {
+		return -1;
+	}
+
+	return 0;
+}
diff --git a/res/res_sip/security_events.c b/res/res_sip/security_events.c
new file mode 100644
index 0000000000..068e8551ff
--- /dev/null
+++ b/res/res_sip/security_events.c
@@ -0,0 +1,234 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Joshua Colp <jcolp@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ *
+ * \brief Generate security events in the PJSIP channel
+ *
+ * \author Joshua Colp <jcolp@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <pjsip.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/security_events.h"
+
+static int find_transport_in_use(void *obj, void *arg, int flags)
+{
+	struct ast_sip_transport *transport = obj;
+	pjsip_rx_data *rdata = arg;
+
+	if ((transport->state->transport == rdata->tp_info.transport) ||
+		(transport->state->factory && !pj_strcmp(&transport->state->factory->addr_name.host, &rdata->tp_info.transport->local_name.host) &&
+			transport->state->factory->addr_name.port == rdata->tp_info.transport->local_name.port)) {
+		return CMP_MATCH | CMP_STOP;
+	}
+
+	return 0;
+}
+
+static enum ast_transport security_event_get_transport(pjsip_rx_data *rdata)
+{
+	RAII_VAR(struct ao2_container *, transports, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
+
+	/* It should be impossible for these to fail as the transport has to exist for the message to exist */
+	transports = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "transport", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+
+	ast_assert(transports != NULL);
+
+	transport = ao2_callback(transports, 0, find_transport_in_use, rdata);
+
+	ast_assert(transport != NULL);
+
+	return transport->type;
+}
+
+static void security_event_populate(pjsip_rx_data *rdata, char *call_id, size_t call_id_size, struct ast_sockaddr *local, struct ast_sockaddr *remote)
+{
+	char host[NI_MAXHOST];
+
+	ast_copy_pj_str(call_id, &rdata->msg_info.cid->id, call_id_size);
+
+	ast_copy_pj_str(host, &rdata->tp_info.transport->local_name.host, sizeof(host));
+	ast_sockaddr_parse(local, host, PARSE_PORT_FORBID);
+	ast_sockaddr_set_port(local, rdata->tp_info.transport->local_name.port);
+
+	ast_sockaddr_parse(remote, rdata->pkt_info.src_name, PARSE_PORT_FORBID);
+	ast_sockaddr_set_port(remote, rdata->pkt_info.src_port);
+}
+
+void ast_sip_report_invalid_endpoint(const char *name, pjsip_rx_data *rdata)
+{
+	enum ast_transport transport = security_event_get_transport(rdata);
+	char call_id[pj_strlen(&rdata->msg_info.cid->id) + 1];
+	struct ast_sockaddr local, remote;
+
+	struct ast_security_event_inval_acct_id inval_acct_id = {
+		.common.event_type = AST_SECURITY_EVENT_INVAL_ACCT_ID,
+		.common.version    = AST_SECURITY_EVENT_INVAL_ACCT_ID_VERSION,
+		.common.service    = "PJSIP",
+		.common.account_id = name,
+		.common.local_addr = {
+			.addr      = &local,
+			.transport = transport,
+		},
+		.common.remote_addr = {
+			.addr       = &remote,
+			.transport = transport,
+		},
+		.common.session_id = call_id,
+	};
+
+	security_event_populate(rdata, call_id, sizeof(call_id), &local, &remote);
+
+	ast_security_event_report(AST_SEC_EVT(&inval_acct_id));
+}
+
+void ast_sip_report_failed_acl(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, const char *name)
+{
+	enum ast_transport transport = security_event_get_transport(rdata);
+	char call_id[pj_strlen(&rdata->msg_info.cid->id) + 1];
+	struct ast_sockaddr local, remote;
+
+	struct ast_security_event_failed_acl failed_acl_event = {
+			.common.event_type  = AST_SECURITY_EVENT_FAILED_ACL,
+			.common.version     = AST_SECURITY_EVENT_FAILED_ACL_VERSION,
+			.common.service     = "PJSIP",
+			.common.account_id  = ast_sorcery_object_get_id(endpoint),
+			.common.local_addr  = {
+					.addr       = &local,
+					.transport  = transport,
+			},
+			.common.remote_addr = {
+					.addr       = &remote,
+					.transport  = transport,
+			},
+			.common.session_id  = call_id,
+			.acl_name           = name,
+	};
+
+	security_event_populate(rdata, call_id, sizeof(call_id), &local, &remote);
+
+	ast_security_event_report(AST_SEC_EVT(&failed_acl_event));
+}
+
+void ast_sip_report_auth_failed_challenge_response(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata)
+{
+	pjsip_authorization_hdr *auth = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_AUTHORIZATION, NULL);
+	enum ast_transport transport = security_event_get_transport(rdata);
+	char call_id[pj_strlen(&rdata->msg_info.cid->id) + 1];
+	char nonce[64] = "", response[256] = "";
+	struct ast_sockaddr local, remote;
+
+	struct ast_security_event_chal_resp_failed chal_resp_failed = {
+				.common.event_type = AST_SECURITY_EVENT_CHAL_RESP_FAILED,
+				.common.version    = AST_SECURITY_EVENT_CHAL_RESP_FAILED_VERSION,
+				.common.service    = "PJSIP",
+				.common.account_id = ast_sorcery_object_get_id(endpoint),
+				.common.local_addr = {
+						.addr      = &local,
+						.transport = transport,
+				},
+				.common.remote_addr = {
+						.addr      = &remote,
+						.transport = transport,
+				},
+				.common.session_id = call_id,
+
+				.challenge         = nonce,
+				.response          = response,
+				.expected_response = "",
+		};
+
+	if (auth && !pj_strcmp2(&auth->scheme, "digest")) {
+		ast_copy_pj_str(nonce, &auth->credential.digest.nonce, sizeof(nonce));
+		ast_copy_pj_str(response, &auth->credential.digest.response, sizeof(response));
+	}
+
+	security_event_populate(rdata, call_id, sizeof(call_id), &local, &remote);
+
+	ast_security_event_report(AST_SEC_EVT(&chal_resp_failed));
+}
+
+void ast_sip_report_auth_success(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata)
+{
+	pjsip_authorization_hdr *auth = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_AUTHORIZATION, NULL);
+	enum ast_transport transport = security_event_get_transport(rdata);
+	char call_id[pj_strlen(&rdata->msg_info.cid->id) + 1];
+	struct ast_sockaddr local, remote;
+
+	struct ast_security_event_successful_auth successful_auth = {
+			.common.event_type  = AST_SECURITY_EVENT_SUCCESSFUL_AUTH,
+			.common.version     = AST_SECURITY_EVENT_SUCCESSFUL_AUTH_VERSION,
+			.common.service     = "PJSIP",
+			.common.account_id  = ast_sorcery_object_get_id(endpoint),
+			.common.local_addr  = {
+					.addr       = &local,
+					.transport  = transport,
+			},
+			.common.remote_addr = {
+					.addr       = &remote,
+					.transport  = transport,
+			},
+			.common.session_id  = call_id,
+			.using_password     = auth ? (uint32_t *)1 : (uint32_t *)0,
+	};
+
+	security_event_populate(rdata, call_id, sizeof(call_id), &local, &remote);
+
+	ast_security_event_report(AST_SEC_EVT(&successful_auth));
+}
+
+void ast_sip_report_auth_challenge_sent(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, pjsip_tx_data *tdata)
+{
+	pjsip_www_authenticate_hdr *auth = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_WWW_AUTHENTICATE, NULL);
+	enum ast_transport transport = security_event_get_transport(rdata);
+	char nonce[64] = "", call_id[pj_strlen(&rdata->msg_info.cid->id) + 1];
+	struct ast_sockaddr local, remote;
+
+	struct ast_security_event_chal_sent chal_sent = {
+			.common.event_type = AST_SECURITY_EVENT_CHAL_SENT,
+			.common.version    = AST_SECURITY_EVENT_CHAL_SENT_VERSION,
+			.common.service    = "PJSIP",
+			.common.account_id = ast_sorcery_object_get_id(endpoint),
+			.common.local_addr = {
+					.addr      = &local,
+					.transport = transport,
+			},
+			.common.remote_addr = {
+					.addr      = &remote,
+					.transport = transport,
+			},
+			.common.session_id = call_id,
+			.challenge         = nonce,
+	};
+
+	if (auth && !pj_strcmp2(&auth->scheme, "digest")) {
+		ast_copy_pj_str(nonce, &auth->challenge.digest.nonce, sizeof(nonce));
+	}
+
+	security_event_populate(rdata, call_id, sizeof(call_id), &local, &remote);
+
+	ast_security_event_report(AST_SEC_EVT(&chal_sent));
+}
diff --git a/res/res_sip/sip_configuration.c b/res/res_sip/sip_configuration.c
index 3488d527e0..5864bdeecc 100644
--- a/res/res_sip/sip_configuration.c
+++ b/res/res_sip/sip_configuration.c
@@ -134,8 +134,93 @@ static char *handle_cli_show_endpoints(struct ast_cli_entry *e, int cmd, struct
 	return CLI_SUCCESS;
 }
 
+static int show_contact(void *obj, void *arg, int flags)
+{
+	struct ast_sip_contact *contact = obj;
+	struct ast_cli_args *a = arg;
+	RAII_VAR(struct ast_sip_contact_status *, status, ast_sorcery_retrieve_by_id(
+			 ast_sip_get_sorcery(), CONTACT_STATUS,
+			 ast_sorcery_object_get_id(contact)), ao2_cleanup);
+
+	ast_cli(a->fd, "\tContact %s:\n", contact->uri);
+
+	if (!status) {
+		ast_cli(a->fd, "\tStatus not found!\n");
+		return 0;
+	}
+
+	ast_cli(a->fd, "\t\tavailable = %s\n", status->status ? "yes" : "no");
+
+	if (status->status) {
+		ast_cli(a->fd, "\t\tRTT = %lld microseconds\n", (long long)status->rtt);
+	}
+
+	return 0;
+}
+
+static void show_endpoint(struct ast_sip_endpoint *endpoint, struct ast_cli_args *a)
+{
+	char *aor_name, *aors;
+
+	if (ast_strlen_zero(endpoint->aors)) {
+		return;
+	}
+
+	aors = ast_strdupa(endpoint->aors);
+
+	while ((aor_name = strsep(&aors, ","))) {
+		RAII_VAR(struct ast_sip_aor *, aor,
+			 ast_sip_location_retrieve_aor(aor_name), ao2_cleanup);
+		RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
+
+		if (!aor || !(contacts = ast_sip_location_retrieve_aor_contacts(aor))) {
+			continue;
+		}
+
+		ast_cli(a->fd, "AOR %s:\n", ast_sorcery_object_get_id(aor));
+		ao2_callback(contacts, OBJ_NODATA, show_contact, a);
+	}
+
+	return;
+}
+
+static char *cli_show_endpoint(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
+	const char *endpoint_name;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "sip show endpoint";
+		e->usage =
+			"Usage: sip show endpoint <endpoint>\n"
+			"       Show the given SIP endpoint.\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 4) {
+		return CLI_SHOWUSAGE;
+	}
+
+	endpoint_name = a->argv[3];
+
+	if (!(endpoint = ast_sorcery_retrieve_by_id(
+		      ast_sip_get_sorcery(), "endpoint", endpoint_name))) {
+		ast_cli(a->fd, "Unable to retrieve endpoint %s\n", endpoint_name);
+		return CLI_FAILURE;
+	}
+
+	ast_cli(a->fd, "Endpoint %s:\n", endpoint_name);
+	show_endpoint(endpoint, a);
+
+	return CLI_SUCCESS;
+}
+
 static struct ast_cli_entry cli_commands[] = {
 	AST_CLI_DEFINE(handle_cli_show_endpoints, "Show SIP Endpoints"),
+	AST_CLI_DEFINE(cli_show_endpoint, "Show SIP Endpoint")
 };
 
 static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
@@ -291,6 +376,22 @@ static int direct_media_method_handler(const struct aco_option *opt, struct ast_
 	return 0;
 }
 
+static int connected_line_method_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct ast_sip_endpoint *endpoint = obj;
+
+	if (!strcasecmp(var->value, "invite") || !strcasecmp(var->value, "reinvite")) {
+		endpoint->connected_line_method = AST_SIP_SESSION_REFRESH_METHOD_INVITE;
+	} else if (!strcasecmp(var->value, "update")) {
+		endpoint->connected_line_method = AST_SIP_SESSION_REFRESH_METHOD_UPDATE;
+	} else {
+		ast_log(LOG_NOTICE, "Unrecognized option value %s for %s on endpoint %s\n",
+				var->value, var->name, ast_sorcery_object_get_id(endpoint));
+		return -1;
+	}
+	return 0;
+}
+
 static int direct_media_glare_mitigation_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
 	struct ast_sip_endpoint *endpoint = obj;
@@ -353,6 +454,65 @@ static int caller_id_tag_handler(const struct aco_option *opt, struct ast_variab
 	return endpoint->id.tag ? 0 : -1;
 }
 
+static int media_encryption_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct ast_sip_endpoint *endpoint = obj;
+
+	if (!strcasecmp("no", var->value)) {
+		endpoint->media_encryption = AST_SIP_MEDIA_ENCRYPT_NONE;
+	} else if (!strcasecmp("sdes", var->value)) {
+		endpoint->media_encryption = AST_SIP_MEDIA_ENCRYPT_SDES;
+	/*} else if (!strcasecmp("dtls", var->value)) {
+		endpoint->media_encryption = AST_SIP_MEDIA_ENCRYPT_DTLS;*/
+	} else {
+		return -1;
+	}
+
+	return 0;
+}
+
+static int group_handler(const struct aco_option *opt,
+			 struct ast_variable *var, void *obj)
+{
+	struct ast_sip_endpoint *endpoint = obj;
+
+	if (!strncmp(var->name, "callgroup", 9)) {
+		if (!(endpoint->callgroup = ast_get_group(var->value))) {
+			return -1;
+		}
+	} else if (!strncmp(var->name, "pickupgroup", 11)) {
+		if (!(endpoint->pickupgroup = ast_get_group(var->value))) {
+			return -1;
+		}
+	} else {
+		return -1;
+	}
+
+	return 0;
+}
+
+static int named_groups_handler(const struct aco_option *opt,
+				struct ast_variable *var, void *obj)
+{
+	struct ast_sip_endpoint *endpoint = obj;
+
+	if (!strncmp(var->name, "namedcallgroup", 14)) {
+		if (!(endpoint->named_callgroups =
+		      ast_get_namedgroups(var->value))) {
+			return -1;
+		}
+	} else if (!strncmp(var->name, "namedpickupgroup", 16)) {
+		if (!(endpoint->named_pickupgroups =
+		      ast_get_namedgroups(var->value))) {
+			return -1;
+		}
+	} else {
+		return -1;
+	}
+
+	return 0;
+}
+
 static void *sip_nat_hook_alloc(const char *name)
 {
 	return ao2_alloc(sizeof(struct ast_sip_nat_hook), NULL);
@@ -450,7 +610,6 @@ int ast_res_sip_initialize_configuration(void)
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "context", "default", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, context));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "disallow", "", OPT_CODEC_T, 0, FLDSET(struct ast_sip_endpoint, prefs, codecs));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow", "", OPT_CODEC_T, 1, FLDSET(struct ast_sip_endpoint, prefs, codecs));
-	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "qualify_frequency", 0, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_endpoint, qualify_frequency), 0, 86400);
 	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtmfmode", "rfc4733", dtmf_handler, NULL, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_ipv6", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, rtp_ipv6));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_symmetric", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, rtp_symmetric));
@@ -472,6 +631,7 @@ int ast_res_sip_initialize_configuration(void)
 	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "identify_by", "username,location", ident_handler, NULL, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "direct_media", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, direct_media));
 	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "direct_media_method", "invite", direct_media_method_handler, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "connected_line_method", "invite", connected_line_method_handler, NULL, 0, 0);
 	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "direct_media_glare_mitigation", "none", direct_media_glare_mitigation_handler, NULL, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "disable_direct_media_on_nat", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, disable_direct_media_on_nat));
 	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callerid", "", caller_id_handler, NULL, 0, 0);
@@ -481,8 +641,17 @@ int ast_res_sip_initialize_configuration(void)
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "trust_id_outbound", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, trust_id_outbound));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_pai", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, send_pai));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_rpid", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, send_rpid));
+	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_diversion", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, send_diversion));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "mailboxes", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, mailboxes));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "aggregate_mwi", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, aggregate_mwi));
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "media_encryption", "no", media_encryption_handler, NULL, 0, 0);
+	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "use_avpf", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, use_avpf));
+	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "one_touch_recording", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, one_touch_recording));
+	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "inband_progress", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, inband_progress));
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "callgroup", "", group_handler, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "pickupgroup", "", group_handler, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "namedcallgroup", "", named_groups_handler, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "namedpickupgroup", "", named_groups_handler, NULL, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "devicestate_busy_at", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, devicestate_busy_at));
 
 	if (ast_sip_initialize_sorcery_transport(sip_sorcery)) {
@@ -499,6 +668,13 @@ int ast_res_sip_initialize_configuration(void)
 		return -1;
 	}
 
+	if (ast_sip_initialize_sorcery_qualify(sip_sorcery)) {
+		ast_log(LOG_ERROR, "Failed to register SIP qualify support with sorcery\n");
+		ast_sorcery_unref(sip_sorcery);
+		sip_sorcery = NULL;
+		return -1;
+	}
+
 	ast_sorcery_observer_add(sip_sorcery, "contact", &state_contact_observer);
 
 	if (ast_sip_initialize_sorcery_domain_alias(sip_sorcery)) {
@@ -539,6 +715,8 @@ static void endpoint_destructor(void* obj)
 	destroy_auths(endpoint->sip_inbound_auths, endpoint->num_inbound_auths);
 	destroy_auths(endpoint->sip_outbound_auths, endpoint->num_outbound_auths);
 	ast_party_id_free(&endpoint->id);
+	endpoint->named_callgroups = ast_unref_namedgroups(endpoint->named_callgroups);
+	endpoint->named_pickupgroups = ast_unref_namedgroups(endpoint->named_pickupgroups);
 	ao2_cleanup(endpoint->persistent);
 }
 
@@ -596,4 +774,3 @@ struct ast_sorcery *ast_sip_get_sorcery(void)
 {
 	return sip_sorcery;
 }
-
diff --git a/res/res_sip/sip_distributor.c b/res/res_sip/sip_distributor.c
index 7662610897..db36b61821 100644
--- a/res/res_sip/sip_distributor.c
+++ b/res/res_sip/sip_distributor.c
@@ -140,11 +140,21 @@ static pj_bool_t endpoint_lookup(pjsip_rx_data *rdata)
 	}
 
 	if (!endpoint && !is_ack) {
+		char name[AST_UUID_STR_LEN] = "";
+		pjsip_uri *from = rdata->msg_info.from->uri;
+
 		/* XXX When we do an alwaysauthreject-like option, we'll need to take that into account
 		 * for this response. Either that, or have a pseudo-endpoint to pass along so that authentication
 		 * will fail
 		 */
 		pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
+
+		if (PJSIP_URI_SCHEME_IS_SIP(from) || PJSIP_URI_SCHEME_IS_SIPS(from)) {
+			pjsip_sip_uri *sip_from = pjsip_uri_get_uri(from);
+			ast_copy_pj_str(name, &sip_from->user, sizeof(name));
+		}
+
+		ast_sip_report_invalid_endpoint(name, rdata);
 		return PJ_TRUE;
 	}
 	rdata->endpt_info.mod_data[endpoint_mod.id] = endpoint;
@@ -164,16 +174,20 @@ static pj_bool_t authenticate(pjsip_rx_data *rdata)
 		switch (ast_sip_check_authentication(endpoint, rdata, tdata)) {
 		case AST_SIP_AUTHENTICATION_CHALLENGE:
 			/* Send the 401 we created for them */
+			ast_sip_report_auth_challenge_sent(endpoint, rdata, tdata);
 			pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL);
 			return PJ_TRUE;
 		case AST_SIP_AUTHENTICATION_SUCCESS:
+			ast_sip_report_auth_success(endpoint, rdata);
 			pjsip_tx_data_dec_ref(tdata);
 			return PJ_FALSE;
 		case AST_SIP_AUTHENTICATION_FAILED:
+			ast_sip_report_auth_failed_challenge_response(endpoint, rdata);
 			pjsip_tx_data_dec_ref(tdata);
 			pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
 			return PJ_TRUE;
 		case AST_SIP_AUTHENTICATION_ERROR:
+			ast_sip_report_auth_failed_challenge_response(endpoint, rdata);
 			pjsip_tx_data_dec_ref(tdata);
 			pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
 			return PJ_TRUE;
diff --git a/res/res_sip/sip_options.c b/res/res_sip/sip_options.c
index 5e3f8edcac..4c8a9f6a79 100644
--- a/res/res_sip/sip_options.c
+++ b/res/res_sip/sip_options.c
@@ -1,8 +1,19 @@
 /*
- * sip_options.c
+ * Asterisk -- An open source telephony toolkit.
  *
- *  Created on: Jan 25, 2013
- *      Author: mjordan
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Matt Jordan <mjordan@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
  */
 
 #include "asterisk.h"
@@ -16,41 +27,429 @@
 #include "asterisk/pbx.h"
 #include "asterisk/astobj2.h"
 #include "asterisk/cli.h"
+#include "asterisk/time.h"
 #include "include/res_sip_private.h"
 
 #define DEFAULT_LANGUAGE "en"
 #define DEFAULT_ENCODING "text/plain"
 #define QUALIFIED_BUCKETS 211
 
-/*! \brief Scheduling context for qualifies */
-static struct ast_sched_context *sched; /* XXX move this to registrar */
+static int qualify_contact(struct ast_sip_contact *contact);
+
+/*!
+ * \internal
+ * \brief Create a ast_sip_contact_status object.
+ */
+static void *contact_status_alloc(const char *name)
+{
+	struct ast_sip_contact_status *status = ao2_alloc_options(
+		sizeof(*status), NULL, AO2_ALLOC_OPT_LOCK_NOLOCK);
+
+	if (!status) {
+		ast_log(LOG_ERROR, "Unable to allocate ast_sip_contact_status\n");
+		return NULL;
+	}
+
+	status->status = UNAVAILABLE;
+
+	return status;
+}
+
+/*!
+ * \internal
+ * \brief Retrieve a ast_sip_contact_status object from sorcery creating
+ *        one if not found.
+ */
+static struct ast_sip_contact_status *find_or_create_contact_status(const struct ast_sip_contact *contact)
+{
+	struct ast_sip_contact_status *status = ast_sorcery_retrieve_by_id(
+		ast_sip_get_sorcery(), CONTACT_STATUS,
+		ast_sorcery_object_get_id(contact));
+
+	if (status) {
+		return status;
+	}
+
+	if (!(status = ast_sorcery_alloc(
+		      ast_sip_get_sorcery(), CONTACT_STATUS,
+		      ast_sorcery_object_get_id(contact)))) {
+
+		ast_log(LOG_ERROR, "Unable to create ast_sip_contact_status for contact %s\n",
+			contact->uri);
+		return NULL;
+	}
+
+	if (ast_sorcery_create(ast_sip_get_sorcery(), status)) {
+		ast_log(LOG_ERROR, "Unable to persist ast_sip_contact_status for contact %s\n",
+			contact->uri);
+		return NULL;
+	}
+
+	return status;
+}
+
+/*!
+ * \internal
+ * \brief Update an ast_sip_contact_status's elements.
+ */
+static void update_contact_status(const struct ast_sip_contact *contact,
+				  enum ast_sip_contact_status_type value)
+{
+	RAII_VAR(struct ast_sip_contact_status *, status,
+		 find_or_create_contact_status(contact), ao2_cleanup);
+
+	RAII_VAR(struct ast_sip_contact_status *, update, ast_sorcery_alloc(
+		      ast_sip_get_sorcery(), CONTACT_STATUS,
+		      ast_sorcery_object_get_id(status)), ao2_cleanup);
+
+	if (!update) {
+		ast_log(LOG_ERROR, "Unable to create update ast_sip_contact_status for contact %s\n",
+			contact->uri);
+		return;
+	}
+
+	update->status = value;
+
+	/* if the contact is available calculate the rtt as
+	   the diff between the last start time and "now" */
+	update->rtt = update->status ?
+		ast_tvdiff_us(ast_tvnow(), status->rtt_start) : 0;
+
+	update->rtt_start = ast_tv(0, 0);
+
+	if (ast_sorcery_update(ast_sip_get_sorcery(), update)) {
+		ast_log(LOG_ERROR, "Unable to update ast_sip_contact_status for contact %s\n",
+			contact->uri);
+	}
+}
+
+/*!
+ * \internal
+ * \brief Initialize the start time on a contact status so the round
+ *        trip time can be calculated upon a valid response.
+ */
+static void init_start_time(const struct ast_sip_contact *contact)
+{
+	RAII_VAR(struct ast_sip_contact_status *, status,
+		 find_or_create_contact_status(contact), ao2_cleanup);
+
+	RAII_VAR(struct ast_sip_contact_status *, update, ast_sorcery_alloc(
+		      ast_sip_get_sorcery(), CONTACT_STATUS,
+		      ast_sorcery_object_get_id(status)), ao2_cleanup);
+
+	if (!update) {
+		ast_log(LOG_ERROR, "Unable to create update ast_sip_contact_status for contact %s\n",
+			contact->uri);
+		return;
+	}
+
+	update->rtt_start = ast_tvnow();
+
+	if (ast_sorcery_update(ast_sip_get_sorcery(), update)) {
+		ast_log(LOG_ERROR, "Unable to update ast_sip_contact_status for contact %s\n",
+			contact->uri);
+	}
+}
+
+/*!
+ * \internal
+ * \brief For an endpoint try to match on a given contact.
+ */
+static int on_endpoint(void *obj, void *arg, int flags)
+{
+	struct ast_sip_endpoint *endpoint = obj;
+	char *aor_name, *aors;
+
+	if (!arg || ast_strlen_zero(endpoint->aors)) {
+		return 0;
+	}
+
+	aors = ast_strdupa(endpoint->aors);
+
+	while ((aor_name = strsep(&aors, ","))) {
+		RAII_VAR(struct ast_sip_aor *, aor,
+			 ast_sip_location_retrieve_aor(aor_name), ao2_cleanup);
+		RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
+
+		if (!aor || !(contacts = ast_sip_location_retrieve_aor_contacts(aor))) {
+			continue;
+		}
+
+		if (ao2_find(contacts, arg, OBJ_NODATA | OBJ_POINTER)) {
+			return CMP_MATCH;
+		}
+	}
+
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Find endpoints associated with the given contact.
+ */
+static struct ao2_container *find_endpoints(struct ast_sip_contact *contact)
+{
+	RAII_VAR(struct ao2_container *, endpoints,
+		 ast_res_sip_get_endpoints(), ao2_cleanup);
+
+	return ao2_callback(endpoints, OBJ_MULTIPLE, on_endpoint, contact);
+}
+
+/*!
+ * \internal
+ * \brief Receive an response to the qualify contact request.
+ */
+static void qualify_contact_cb(void *token, pjsip_event *e)
+{
+	RAII_VAR(struct ast_sip_contact *, contact, token, ao2_cleanup);
+	RAII_VAR(struct ao2_container *, endpoints, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
+
+	pjsip_transaction *tsx = e->body.tsx_state.tsx;
+	pjsip_rx_data *challenge = e->body.tsx_state.src.rdata;
+	pjsip_tx_data *tdata;
+
+	switch(e->body.tsx_state.type) {
+	case PJSIP_EVENT_TRANSPORT_ERROR:
+	case PJSIP_EVENT_TIMER:
+		update_contact_status(contact, UNAVAILABLE);
+		return;
+	default:
+		break;
+	}
+
+	if (!contact->authenticate_qualify || (tsx->status_code != 401 &&
+					       tsx->status_code != 407)) {
+		update_contact_status(contact, AVAILABLE);
+		return;
+	}
+
+	/* try to find endpoints that are associated with the contact */
+	if (!(endpoints = find_endpoints(contact))) {
+		ast_log(LOG_ERROR, "No endpoints found for contact %s, cannot authenticate",
+			contact->uri);
+		return;
+	}
+
+	/* find "first" endpoint in order to authenticate - actually any
+	   endpoint should do that matched on the contact */
+	endpoint = ao2_callback(endpoints, 0, NULL, NULL);
+
+	if (!ast_sip_create_request_with_auth(endpoint->sip_outbound_auths,
+					      endpoint->num_outbound_auths,
+					      challenge, tsx, &tdata)) {
+		pjsip_endpt_send_request(ast_sip_get_pjsip_endpoint(), tdata,
+					 -1, NULL, NULL);
+	}
+}
+
+/*!
+ * \internal
+ * \brief Attempt to qualify the contact
+ *
+ * \detail Sends a SIP OPTIONS request to the given contact in order to make
+ *         sure that contact is available.
+ */
+static int qualify_contact(struct ast_sip_contact *contact)
+{
+	pjsip_tx_data *tdata;
+
+	if (ast_sip_create_request("OPTIONS", NULL, NULL, contact->uri, &tdata)) {
+		ast_log(LOG_ERROR, "Unable to create request to qualify contact %s\n",
+			contact->uri);
+		return -1;
+	}
+
+	init_start_time(contact);
+
+	ao2_ref(contact, +1);
+	if (pjsip_endpt_send_request(ast_sip_get_pjsip_endpoint(),
+				     tdata, -1, contact, qualify_contact_cb) != PJ_SUCCESS) {
+		pjsip_tx_data_dec_ref(tdata);
+		ast_log(LOG_ERROR, "Unable to send request to qualify contact %s\n",
+			contact->uri);
+		ao2_ref(contact, -1);
+		return -1;
+	}
 
-struct ao2_container *scheduled_qualifies;
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Scheduling context for sending QUALIFY request at specified intervals.
+ */
+static struct ast_sched_context *sched;
 
-struct qualify_info {
-	AST_DECLARE_STRING_FIELDS(
-		AST_STRING_FIELD(endpoint_id);
-	);
-	char *scheduler_data;
-	int scheduler_id;
+/*!
+ * \internal
+ * \brief Container to hold all actively scheduled qualifies.
+ */
+static struct ao2_container *sched_qualifies;
+
+/*!
+ * \internal
+ * \brief Structure to hold qualify contact scheduling information.
+ */
+struct sched_data {
+	/*! The scheduling id */
+	int id;
+	/*! The the contact being checked */
+	struct ast_sip_contact *contact;
 };
 
-static pj_bool_t options_module_start(void);
-static pj_bool_t options_module_stop(void);
-static pj_bool_t options_module_on_rx_request(pjsip_rx_data *rdata);
-static pj_bool_t options_module_on_rx_response(pjsip_rx_data *rdata);
+/*!
+ * \internal
+ * \brief Destroy the scheduled data and remove from scheduler.
+ */
+static void sched_data_destructor(void *obj)
+{
+	struct sched_data *data = obj;
+	ao2_cleanup(data->contact);
+}
+/*!
+ * \internal
+ * \brief Create the scheduling data object.
+ */
+static struct sched_data *sched_data_create(struct ast_sip_contact *contact)
+{
+	struct sched_data *data = ao2_alloc(sizeof(*data), sched_data_destructor);
 
-static pjsip_module options_module = {
-	.name = {"Options Module", 14},
-	.id = -1,
-	.priority = PJSIP_MOD_PRIORITY_APPLICATION,
-	.start = options_module_start,
-	.stop = options_module_stop,
-	.on_rx_request = options_module_on_rx_request,
-	.on_rx_response = options_module_on_rx_response,
+	if (!data) {
+		ast_log(LOG_ERROR, "Unable to create schedule qualify data\n");
+		return NULL;
+	}
+
+	data->contact = contact;
+	ao2_ref(data->contact, +1);
+
+	return data;
+}
+
+/*!
+ * \internal
+ * \brief Send a qualify contact request within a threaded task.
+ */
+static int qualify_contact_task(void *obj)
+{
+	RAII_VAR(struct ast_sip_contact *, contact, obj, ao2_cleanup);
+	return qualify_contact(contact);
+}
+
+/*!
+ * \internal
+ * \brief Send a scheduled qualify contact request.
+ */
+static int qualify_contact_sched(const void *obj)
+{
+	struct sched_data *data = (struct sched_data *)obj;
+
+	ao2_ref(data->contact, +1);
+	if (ast_sip_push_task(NULL, qualify_contact_task, data->contact)) {
+		ao2_ref(data->contact, -1);
+		ao2_cleanup(data);
+		return 0;
+	}
+
+	return data->contact->qualify_frequency * 1000;
+}
+
+/*!
+ * \internal
+ * \brief Set up a scheduled qualify contact check.
+ */
+static void schedule_qualify(struct ast_sip_contact *contact)
+{
+	RAII_VAR(struct sched_data *, data, sched_data_create(contact), ao2_cleanup);
+
+	if (!data) {
+		return;
+	}
+
+	ao2_ref(data, +1);
+	if ((data->id = ast_sched_add_variable(
+		    sched, contact->qualify_frequency * 1000,
+		    qualify_contact_sched, data, 1)) < 0) {
+
+		ao2_ref(data, -1);
+		ast_log(LOG_ERROR, "Unable to schedule qualify for contact %s\n",
+			contact->uri);
+		return;
+	}
+
+	ao2_link(sched_qualifies, data);
+}
+
+/*!
+ * \internal
+ * \brief Remove the contact from the scheduler.
+ */
+static void unschedule_qualify(struct ast_sip_contact *contact)
+{
+	RAII_VAR(struct sched_data *, data, ao2_find(
+			 sched_qualifies, contact, OBJ_UNLINK), ao2_cleanup);
+
+	if (!data) {
+		return;
+	}
+
+	AST_SCHED_DEL_UNREF(sched, data->id, ao2_cleanup(data));
+}
+
+/*!
+ * \internal
+ * \brief Qualify the given contact and set up scheduling if configured.
+ */
+static void qualify_and_schedule(struct ast_sip_contact *contact)
+{
+	unschedule_qualify(contact);
+
+	if (contact->qualify_frequency) {
+		ao2_ref(contact, +1);
+		ast_sip_push_task(NULL, qualify_contact_task, contact);
+
+		schedule_qualify(contact);
+	}
+}
+
+/*!
+ * \internal
+ * \brief A new contact has been created make sure it is available.
+ */
+static void contact_created(const void *obj)
+{
+	qualify_and_schedule((struct ast_sip_contact *)obj);
+}
+
+/*!
+ * \internal
+ * \brief A contact has been deleted remove status tracking.
+ */
+static void contact_deleted(const void *obj)
+{
+	struct ast_sip_contact *contact = (struct ast_sip_contact *)obj;
+	RAII_VAR(struct ast_sip_contact_status *, status, NULL, ao2_cleanup);
+
+	unschedule_qualify(contact);
+
+	if (!(status = ast_sorcery_retrieve_by_id(
+		      ast_sip_get_sorcery(), CONTACT_STATUS,
+		      ast_sorcery_object_get_id(contact)))) {
+		return;
+	}
+
+	if (ast_sorcery_delete(ast_sip_get_sorcery(), status)) {
+		ast_log(LOG_ERROR, "Unable to delete ast_sip_contact_status for contact %s\n",
+			contact->uri);
+	}
+}
+
+struct ast_sorcery_observer contact_observer = {
+	.created = contact_created,
+	.deleted = contact_deleted
 };
 
-static pj_bool_t options_module_start(void)
+static pj_bool_t options_start(void)
 {
 	if (!(sched = ast_sched_context_create()) ||
 	    ast_sched_start_thread(sched)) {
@@ -60,9 +459,11 @@ static pj_bool_t options_module_start(void)
 	return PJ_SUCCESS;
 }
 
-static pj_bool_t options_module_stop(void)
+static pj_bool_t options_stop(void)
 {
-	ao2_t_ref(scheduled_qualifies, -1, "Remove scheduled qualifies on module stop");
+	ast_sorcery_observer_remove(ast_sip_get_sorcery(), "contact", &contact_observer);
+
+	ao2_t_ref(sched_qualifies, -1, "Remove scheduled qualifies on module stop");
 
 	if (sched) {
 		ast_sched_context_destroy(sched);
@@ -71,18 +472,20 @@ static pj_bool_t options_module_stop(void)
 	return PJ_SUCCESS;
 }
 
-static pj_status_t send_options_response(pjsip_rx_data *rdata, pjsip_dialog *pj_dlg, int code)
+static pj_status_t send_options_response(pjsip_rx_data *rdata, int code)
 {
 	pjsip_endpoint *endpt = ast_sip_get_pjsip_endpoint();
-	pjsip_transaction *pj_trans = pjsip_rdata_get_tsx(rdata);
+	pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata);
+	pjsip_transaction *trans = pjsip_rdata_get_tsx(rdata);
 	pjsip_tx_data *tdata;
 	const pjsip_hdr *hdr;
 	pjsip_response_addr res_addr;
 	pj_status_t status;
 
 	/* Make the response object */
-	status = pjsip_endpt_create_response(endpt, rdata, code, NULL, &tdata);
-	if (status != PJ_SUCCESS) {
+	if ((status = pjsip_endpt_create_response(
+		     endpt, rdata, code, NULL, &tdata) != PJ_SUCCESS)) {
+		ast_log(LOG_ERROR, "Unable to create response (%d)\n", status);
 		return status;
 	}
 
@@ -106,262 +509,267 @@ static pj_status_t send_options_response(pjsip_rx_data *rdata, pjsip_dialog *pj_
 	ast_sip_add_header(tdata, "Accept-Encoding", DEFAULT_ENCODING);
 	ast_sip_add_header(tdata, "Accept-Language", DEFAULT_LANGUAGE);
 
-	if (pj_dlg && pj_trans) {
-		status = pjsip_dlg_send_response(pj_dlg, pj_trans, tdata);
+	if (dlg && trans) {
+		status = pjsip_dlg_send_response(dlg, trans, tdata);
 	} else {
 		/* Get where to send request. */
-		status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
-		if (status != PJ_SUCCESS) {
+		if ((status = pjsip_get_response_addr(
+			     tdata->pool, rdata, &res_addr)) != PJ_SUCCESS) {
+			ast_log(LOG_ERROR, "Unable to get response address (%d)\n",
+				status);
+
 			pjsip_tx_data_dec_ref(tdata);
 			return status;
 		}
-		status = pjsip_endpt_send_response(endpt, &res_addr, tdata, NULL, NULL);
+		status = pjsip_endpt_send_response(endpt, &res_addr, tdata,
+						   NULL, NULL);
+	}
+
+	if (status != PJ_SUCCESS) {
+		ast_log(LOG_ERROR, "Unable to send response (%d)\n", status);
 	}
 
 	return status;
 }
 
-static pj_bool_t options_module_on_rx_request(pjsip_rx_data *rdata)
+static pj_bool_t options_on_rx_request(pjsip_rx_data *rdata)
 {
 	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
-	pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata);
 	pjsip_uri *ruri;
 	pjsip_sip_uri *sip_ruri;
 	char exten[AST_MAX_EXTENSION];
 
-	if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_options_method)) {
+	if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
+			     &pjsip_options_method)) {
+		return PJ_FALSE;
+	}
+
+	if (!(endpoint = ast_pjsip_rdata_get_endpoint(rdata))) {
 		return PJ_FALSE;
 	}
-	endpoint = ast_pjsip_rdata_get_endpoint(rdata);
-	ast_assert(endpoint != NULL);
 
 	ruri = rdata->msg_info.msg->line.req.uri;
 	if (!PJSIP_URI_SCHEME_IS_SIP(ruri) && !PJSIP_URI_SCHEME_IS_SIPS(ruri)) {
-		send_options_response(rdata, dlg, 416);
+		send_options_response(rdata, 416);
 		return -1;
 	}
-	
+
 	sip_ruri = pjsip_uri_get_uri(ruri);
 	ast_copy_pj_str(exten, &sip_ruri->user, sizeof(exten));
 
 	if (ast_shutting_down()) {
-		send_options_response(rdata, dlg, 503);
+		send_options_response(rdata, 503);
 	} else if (!ast_exists_extension(NULL, endpoint->context, exten, 1, NULL)) {
-		send_options_response(rdata, dlg, 404);
+		send_options_response(rdata, 404);
 	} else {
-		send_options_response(rdata, dlg, 200);
+		send_options_response(rdata, 200);
 	}
 	return PJ_TRUE;
 }
 
-static pj_bool_t options_module_on_rx_response(pjsip_rx_data *rdata)
-{
+static pjsip_module options_module = {
+	.name = {"Options Module", 14},
+	.id = -1,
+	.priority = PJSIP_MOD_PRIORITY_APPLICATION,
+	.start = options_start,
+	.stop = options_stop,
+	.on_rx_request = options_on_rx_request,
+};
 
-	return PJ_SUCCESS;
+/*!
+ * \internal
+ * \brief Send qualify request to the given contact.
+ */
+static int cli_on_contact(void *obj, void *arg, int flags)
+{
+	struct ast_sip_contact *contact = obj;
+	struct ast_cli_args *a = arg;
+	ast_cli(a->fd, " contact %s\n", contact->uri);
+	qualify_contact(contact);
+	return 0;
 }
 
-static int qualify_info_hash_fn(const void *obj, int flags)
+/*!
+ * \internal
+ * \brief For an endpoint iterate over and qualify all aors/contacts
+ */
+static void cli_qualify_contacts(struct ast_cli_args *a, const char *endpoint_name,
+				 struct ast_sip_endpoint *endpoint)
 {
-	const struct qualify_info *info = obj;
-	const char *endpoint_id = flags & OBJ_KEY ? obj : info->endpoint_id;
+	char *aor_name, *aors;
 
-	return ast_str_hash(endpoint_id);
-}
+	if (ast_strlen_zero(endpoint->aors)) {
+		ast_cli(a->fd, "Endpoint %s has no AoR's configured\n",
+			endpoint_name);
+		return;
+	}
 
-static int qualify_info_cmp_fn(void *obj, void *arg, int flags)
-{
-	struct qualify_info *left = obj;
-	struct qualify_info *right = arg;
-	const char *right_endpoint_id = flags & OBJ_KEY ? arg : right->endpoint_id;
+	aors = ast_strdupa(endpoint->aors);
 
-	return strcmp(left->endpoint_id, right_endpoint_id) ? 0 : CMP_MATCH | CMP_STOP;
-}
+	while ((aor_name = strsep(&aors, ","))) {
+		RAII_VAR(struct ast_sip_aor *, aor,
+			 ast_sip_location_retrieve_aor(aor_name), ao2_cleanup);
+		RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
 
+		if (!aor || !(contacts = ast_sip_location_retrieve_aor_contacts(aor))) {
+			continue;
+		}
 
-static void qualify_info_destructor(void *obj)
-{
-	struct qualify_info *info = obj;
-	if (!info) {
-		return;
-	}
-	ast_string_field_free_memory(info);
-	/* Cancel the qualify */
-	if (!AST_SCHED_DEL(sched, info->scheduler_id)) {
-		/* If we successfully deleted the qualify, we got it before it
-		 * fired. We can safely delete the data that was passed to it.
-		 * Otherwise, we're getting deleted while this is firing - don't
-		 * touch that memory!
-		 */
-		ast_free(info->scheduler_data);
+		ast_cli(a->fd, "Sending qualify to endpoint %s", endpoint_name);
+		ao2_callback(contacts, OBJ_NODATA, cli_on_contact, a);
 	}
 }
 
-static struct qualify_info *create_qualify_info(struct ast_sip_endpoint *endpoint)
+static char *cli_qualify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-	struct qualify_info *info;
+	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
+	const char *endpoint_name;
 
-	info = ao2_alloc(sizeof(*info), qualify_info_destructor);
-	if (!info) {
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "sip qualify";
+		e->usage =
+			"Usage: sip qualify <endpoint>\n"
+			"       Send a SIP OPTIONS request to all contacts on the endpoint.\n";
+		return NULL;
+	case CLI_GENERATE:
 		return NULL;
 	}
 
-	if (ast_string_field_init(info, 64)) {
-		ao2_ref(info, -1);
-		return NULL;
+	if (a->argc != 3) {
+		return CLI_SHOWUSAGE;
 	}
-	ast_string_field_set(info, endpoint_id, ast_sorcery_object_get_id(endpoint));
 
-	return info;
+	endpoint_name = a->argv[2];
+
+	if (!(endpoint = ast_sorcery_retrieve_by_id(
+		      ast_sip_get_sorcery(), "endpoint", endpoint_name))) {
+		ast_cli(a->fd, "Unable to retrieve endpoint %s\n", endpoint_name);
+		return CLI_FAILURE;
+	}
+
+	/* send a qualify for all contacts registered with the endpoint */
+	cli_qualify_contacts(a, endpoint_name, endpoint);
+
+	return CLI_SUCCESS;
 }
 
-static int send_qualify_request(void *data)
+static struct ast_cli_entry cli_options[] = {
+	AST_CLI_DEFINE(cli_qualify, "Send an OPTIONS request to a SIP endpoint")
+};
+
+static int sched_qualifies_hash_fn(const void *obj, int flags)
 {
-	struct ast_sip_endpoint *endpoint = data;
-	pjsip_tx_data *tdata;
-	/* YAY! Send an OPTIONS request. */
+	const struct sched_data *data = obj;
 
-	ast_sip_create_request("OPTIONS", NULL, endpoint, NULL, &tdata);
-	ast_sip_send_request(tdata, NULL, endpoint);
+	return ast_str_hash(ast_sorcery_object_get_id(data->contact));
+}
 
-	ao2_cleanup(endpoint);
-	return 0;
+static int sched_qualifies_cmp_fn(void *obj, void *arg, int flags)
+{
+	struct sched_data *data = obj;
+
+	return !strcmp(ast_sorcery_object_get_id(data->contact),
+		       ast_sorcery_object_get_id(arg));
 }
 
-static int qualify_endpoint_scheduler_cb(const void *data)
+int ast_sip_initialize_sorcery_qualify(struct ast_sorcery *sorcery)
 {
-	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
-	struct ast_sorcery *sorcery;
-	char *endpoint_id = (char *)data;
+	/* initialize sorcery ast_sip_contact_status resource */
+	ast_sorcery_apply_default(sorcery, CONTACT_STATUS, "memory", NULL);
 
-	sorcery = ast_sip_get_sorcery();
-	if (!sorcery) {
-		ast_free(endpoint_id);
-		return 0;
+	if (ast_sorcery_object_register(sorcery, CONTACT_STATUS,
+					contact_status_alloc, NULL, NULL)) {
+		ast_log(LOG_ERROR, "Unable to register ast_sip_contact_status in sorcery\n");
+		return -1;
 	}
 
-	endpoint = ast_sorcery_retrieve_by_id(sorcery, "endpoint", endpoint_id);
-	if (!endpoint) {
-		/* Whoops, endpoint went away */
-		ast_free(endpoint_id);
-		return 0;
-	}
+	ast_sorcery_object_field_register(sorcery, CONTACT_STATUS, "rtt", "0", OPT_UINT_T,
+					  1, FLDSET(struct ast_sip_contact_status, status));
+	ast_sorcery_object_field_register(sorcery, CONTACT_STATUS, "rtt", "0", OPT_UINT_T,
+					  1, FLDSET(struct ast_sip_contact_status, rtt));
 
-	ast_sip_push_task(NULL, send_qualify_request, endpoint);
+	if (ast_sorcery_observer_add(sorcery, "contact", &contact_observer)) {
+		ast_log(LOG_WARNING, "Unable to add contact observer\n");
+		return -1;
+	}
 
-	return 1;
+	return 0;
 }
 
-static void schedule_qualifies(void)
+static int qualify_and_schedule_cb(void *obj, void *arg, int flags)
 {
-	RAII_VAR(struct ao2_container *, endpoints, NULL, ao2_cleanup);
-	struct ao2_iterator it_endpoints;
-	struct ast_sip_endpoint *endpoint;
-	struct qualify_info *info;
-	char *endpoint_id;
+	struct ast_sip_contact *contact = obj;
+	struct ast_sip_aor *aor = arg;
 
-	endpoints = ast_res_sip_get_endpoints();
-	if (!endpoints) {
-		return;
-	}
+	contact->qualify_frequency = aor->qualify_frequency;
+	qualify_and_schedule(contact);
 
-	it_endpoints = ao2_iterator_init(endpoints, 0);
-	while ((endpoint = ao2_iterator_next(&it_endpoints))) {
-		if (endpoint->qualify_frequency) {
-			/* XXX TODO: This really should only qualify registered peers,
-			 * which means we need a registrar. We should check the
-			 * registrar to see if this endpoint has registered and, if
-			 * not, pass on it.
-			 *
-			 * Actually, all of this should just get moved into the registrar.
-			 * Otherwise, the registar will have to kick this off when a
-			 * new endpoint registers, so it just makes sense to have it
-			 * all live there.
-			 */
-			info = create_qualify_info(endpoint);
-			if (!info) {
-				ao2_ref(endpoint, -1);
-				break;
-			}
-			endpoint_id = ast_strdup(info->endpoint_id);
-			if (!endpoint_id) {
-				ao2_t_ref(info, -1, "Dispose of info on off nominal");
-				ao2_ref(endpoint, -1);
-				break;
-			}
-			info->scheduler_data = endpoint_id;
-			info->scheduler_id = ast_sched_add_variable(sched, endpoint->qualify_frequency * 1000, qualify_endpoint_scheduler_cb, endpoint_id, 1);
-			ao2_t_link(scheduled_qualifies, info, "Link scheduled qualify information into container");
-			ao2_t_ref(info, -1, "Dispose of creation ref");
-		}
-		ao2_t_ref(endpoint, -1, "Dispose of iterator ref");
-	}
-	ao2_iterator_destroy(&it_endpoints);
+	return 0;
 }
 
-static char *send_options(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+/*!
+ * \internal
+ * \brief Qualify and schedule an endpoint's permanent contacts
+ *
+ * \detail For the given endpoint retrieve its list of aors, qualify all
+ *         permanent contacts, and schedule for checks if configured.
+ */
+static int qualify_and_schedule_permanent_cb(void *obj, void *arg, int flags)
 {
-	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
-	const char *endpoint_name;
-	pjsip_tx_data *tdata;
+	struct ast_sip_endpoint *endpoint = obj;
+	char *aor_name, *aors;
 
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "sip send options";
-		e->usage =
-			"Usage: sip send options <endpoint>\n"
-			"       Send a SIP OPTIONS request to the specified endpoint.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
+	if (ast_strlen_zero(endpoint->aors)) {
+		return 0;
 	}
 
-	if (a->argc != 4) {
-		return CLI_SHOWUSAGE;
-	}
+	aors = ast_strdupa(endpoint->aors);
 
-	endpoint_name = a->argv[3];
+	while ((aor_name = strsep(&aors, ","))) {
+		RAII_VAR(struct ast_sip_aor *, aor,
+			 ast_sip_location_retrieve_aor(aor_name), ao2_cleanup);
 
-	endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
-	if (!endpoint) {
-		ast_log(LOG_ERROR, "Unable to retrieve endpoint %s\n", endpoint_name);
-		return CLI_FAILURE;
+		if (!aor || !aor->permanent_contacts) {
+			continue;
+		}
+		ao2_callback(aor->permanent_contacts, OBJ_NODATA, qualify_and_schedule_cb, aor);
 	}
 
-	if (ast_sip_create_request("OPTIONS", NULL, endpoint, NULL, &tdata)) {
-		ast_log(LOG_ERROR, "Unable to create OPTIONS request to endpoint %s\n", endpoint_name);
-		return CLI_FAILURE;
-	}
+	return 0;
+}
 
-	if (ast_sip_send_request(tdata, NULL, endpoint)) {
-		ast_log(LOG_ERROR, "Unable to send OPTIONS request to endpoint %s\n", endpoint_name);
-		return CLI_FAILURE;
-	}
+static void qualify_and_schedule_permanent(void)
+{
+	RAII_VAR(struct ao2_container *, endpoints,
+		 ast_res_sip_get_endpoints(), ao2_cleanup);
 
-	return CLI_SUCCESS;
+	ao2_callback(endpoints, OBJ_NODATA,
+		     qualify_and_schedule_permanent_cb, NULL);
 }
 
-static struct ast_cli_entry cli_options[] = {
-	AST_CLI_DEFINE(send_options, "Send an OPTIONS requst to an arbitrary SIP URI"),
-};
-
 int ast_res_sip_init_options_handling(int reload)
 {
 	const pj_str_t STR_OPTIONS = { "OPTIONS", 7 };
 
-	if (scheduled_qualifies) {
-		ao2_t_ref(scheduled_qualifies, -1, "Remove old scheduled qualifies");
+	if (sched_qualifies) {
+		ao2_t_ref(sched_qualifies, -1, "Remove old scheduled qualifies");
 	}
-	scheduled_qualifies = ao2_t_container_alloc(QUALIFIED_BUCKETS, qualify_info_hash_fn, qualify_info_cmp_fn, "Create container for scheduled qualifies");
-	if (!scheduled_qualifies) {
+
+	if (!(sched_qualifies = ao2_t_container_alloc(
+		QUALIFIED_BUCKETS, sched_qualifies_hash_fn, sched_qualifies_cmp_fn,
+		"Create container for scheduled qualifies"))) {
+
 		return -1;
 	}
 
 	if (reload) {
+		qualify_and_schedule_permanent();
 		return 0;
 	}
 
 	if (pjsip_endpt_register_module(ast_sip_get_pjsip_endpoint(), &options_module) != PJ_SUCCESS) {
-		options_module_stop();
+		options_stop();
 		return -1;
 	}
 
@@ -370,9 +778,8 @@ int ast_res_sip_init_options_handling(int reload)
 		return -1;
 	}
 
+	qualify_and_schedule_permanent();
 	ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options));
 
-	schedule_qualifies();
-
 	return 0;
 }
diff --git a/res/res_sip_caller_id.c b/res/res_sip_caller_id.c
index 22ece0436e..2f40473512 100644
--- a/res/res_sip_caller_id.c
+++ b/res/res_sip_caller_id.c
@@ -688,7 +688,7 @@ static void caller_id_outgoing_response(struct ast_sip_session *session, pjsip_t
 }
 
 static struct ast_sip_session_supplement caller_id_supplement = {
-	.method = "INVITE",
+	.method = "INVITE,UPDATE",
 	.priority = AST_SIP_SESSION_SUPPLEMENT_PRIORITY_CHANNEL - 1000,
 	.incoming_request = caller_id_incoming_request,
 	.incoming_response = caller_id_incoming_response,
diff --git a/res/res_sip_diversion.c b/res/res_sip_diversion.c
new file mode 100644
index 0000000000..70b1fc5084
--- /dev/null
+++ b/res/res_sip_diversion.c
@@ -0,0 +1,346 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Kevin Harwell <kharwell@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>pjproject</depend>
+	<depend>res_sip</depend>
+	<depend>res_sip_session</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_ua.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/res_sip_session.h"
+#include "asterisk/callerid.h"
+#include "asterisk/channel.h"
+#include "asterisk/module.h"
+#include "asterisk/strings.h"
+
+static const pj_str_t diversion_name = { "Diversion", 9 };
+
+/*! \brief Diversion header reasons
+ *
+ * The core defines a bunch of constants used to define
+ * redirecting reasons. This provides a translation table
+ * between those and the strings which may be present in
+ * a SIP Diversion header
+ */
+static const struct reasons {
+	enum AST_REDIRECTING_REASON code;
+	char *const text;
+} reason_table[] = {
+	{ AST_REDIRECTING_REASON_UNKNOWN, "unknown" },
+	{ AST_REDIRECTING_REASON_USER_BUSY, "user-busy" },
+	{ AST_REDIRECTING_REASON_NO_ANSWER, "no-answer" },
+	{ AST_REDIRECTING_REASON_UNAVAILABLE, "unavailable" },
+	{ AST_REDIRECTING_REASON_UNCONDITIONAL, "unconditional" },
+	{ AST_REDIRECTING_REASON_TIME_OF_DAY, "time-of-day" },
+	{ AST_REDIRECTING_REASON_DO_NOT_DISTURB, "do-not-disturb" },
+	{ AST_REDIRECTING_REASON_DEFLECTION, "deflection" },
+	{ AST_REDIRECTING_REASON_FOLLOW_ME, "follow-me" },
+	{ AST_REDIRECTING_REASON_OUT_OF_ORDER, "out-of-service" },
+	{ AST_REDIRECTING_REASON_AWAY, "away" },
+	{ AST_REDIRECTING_REASON_CALL_FWD_DTE, "unknown"},
+	{ AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm"},
+};
+
+static const char *reason_code_to_str(const struct ast_party_redirecting_reason *reason)
+{
+	int code = reason->code;
+
+	/* use specific string if given */
+	if (!ast_strlen_zero(reason->str)) {
+		return reason->str;
+	}
+
+	if (code >= 0 && code < ARRAY_LEN(reason_table)) {
+		return reason_table[code].text;
+	}
+
+	return "unknown";
+}
+
+static enum AST_REDIRECTING_REASON reason_str_to_code(const char *text)
+{
+	enum AST_REDIRECTING_REASON code = AST_REDIRECTING_REASON_UNKNOWN;
+	int i;
+
+	for (i = 0; i < ARRAY_LEN(reason_table); ++i) {
+		if (!strcasecmp(text, reason_table[i].text)) {
+			code = reason_table[i].code;
+			break;
+		}
+	}
+
+	return code;
+}
+
+static pjsip_fromto_hdr *get_diversion_header(pjsip_rx_data *rdata)
+{
+	static const pj_str_t from_name = { "From", 4 };
+
+	pjsip_generic_string_hdr *hdr;
+	pj_str_t value;
+	int size;
+
+	if (!(hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &diversion_name, NULL))) {
+		return NULL;
+	}
+
+	pj_strdup_with_null(rdata->tp_info.pool, &value, &hdr->hvalue);
+
+	/* parse as a fromto header */
+	return pjsip_parse_hdr(rdata->tp_info.pool, &from_name, value.ptr,
+			       pj_strlen(&value), &size);
+}
+
+static void set_redirecting_value(char **dst, const pj_str_t *src)
+{
+	ast_free(*dst);
+	*dst = ast_malloc(pj_strlen(src) + 1);
+	ast_copy_pj_str(*dst, src, pj_strlen(src) + 1);
+}
+
+static void set_redirecting_id(pjsip_name_addr *name_addr, struct ast_party_id *data,
+			       struct ast_set_party_id *update)
+{
+	pjsip_sip_uri *uri = pjsip_uri_get_uri(name_addr->uri);
+
+	if (pj_strlen(&uri->user)) {
+		update->number = 1;
+		data->number.valid = 1;
+		set_redirecting_value(&data->number.str, &uri->user);
+	}
+
+	if (pj_strlen(&name_addr->display)) {
+		update->name = 1;
+		data->name.valid = 1;
+		set_redirecting_value(&data->name.str, &name_addr->display);
+	}
+}
+
+static void copy_redirecting_id(struct ast_party_id *dst, const struct ast_party_id *src,
+				struct ast_set_party_id *update)
+{
+	ast_party_id_copy(dst, src);
+
+	if (dst->number.valid) {
+		update->number = 1;
+	}
+
+	if (dst->name.valid) {
+		update->name = 1;
+	}
+}
+
+static void set_redirecting_reason(pjsip_fromto_hdr *hdr,
+				   struct ast_party_redirecting_reason *data)
+{
+	static const pj_str_t reason_name = { "reason", 6 };
+	pjsip_param *reason = pjsip_param_find(&hdr->other_param, &reason_name);
+
+	if (!reason) {
+		return;
+	}
+
+	set_redirecting_value(&data->str, &reason->value);
+	data->code = reason_str_to_code(data->str);
+}
+
+static void set_redirecting(struct ast_sip_session *session,
+			    pjsip_fromto_hdr *from_info,
+			    pjsip_name_addr *to_info)
+{
+	struct ast_party_redirecting data;
+	struct ast_set_party_redirecting update;
+
+	if (!session->channel) {
+		return;
+	}
+
+	ast_party_redirecting_init(&data);
+	memset(&update, 0, sizeof(update));
+
+	if (from_info) {
+		set_redirecting_id((pjsip_name_addr*)from_info->uri,
+			&data.from, &update.from);
+		set_redirecting_reason(from_info, &data.reason);
+	} else {
+		copy_redirecting_id(&data.from, &session->id, &update.from);
+	}
+
+	set_redirecting_id(to_info, &data.to, &update.to);
+
+	ast_set_party_id_all(&update.priv_orig);
+	ast_set_party_id_all(&update.priv_from);
+	ast_set_party_id_all(&update.priv_to);
+	++data.count;
+
+	ast_channel_set_redirecting(session->channel, &data, &update);
+	ast_party_redirecting_free(&data);
+}
+
+static int diversion_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata)
+{
+	pjsip_fromto_hdr *hdr = get_diversion_header(rdata);
+
+	if (hdr) {
+		set_redirecting(session, hdr, (pjsip_name_addr*)
+				PJSIP_MSG_TO_HDR(rdata->msg_info.msg)->uri);
+	}
+
+	return 0;
+}
+
+static void diversion_incoming_response(struct ast_sip_session *session, pjsip_rx_data *rdata)
+{
+	static const pj_str_t contact_name = { "Contact", 7 };
+
+	pjsip_status_line status = rdata->msg_info.msg->line.status;
+	pjsip_fromto_hdr *div_hdr;
+	pjsip_contact_hdr *contact_hdr;
+
+	if ((status.code != 302) && (status.code != 181)) {
+		return;
+	}
+
+	/* use the diversion header info if there is one. if not one then use the
+           session caller id info. if that doesn't exist use info from the To hdr*/
+	if (!(div_hdr = get_diversion_header(rdata)) && !session->id.number.valid) {
+		div_hdr = PJSIP_MSG_TO_HDR(rdata->msg_info.msg);
+	}
+
+	contact_hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &contact_name, NULL);
+
+	set_redirecting(session, div_hdr, contact_hdr ?	(pjsip_name_addr*)contact_hdr->uri :
+			(pjsip_name_addr*)PJSIP_MSG_FROM_HDR(rdata->msg_info.msg)->uri);
+}
+
+/*!
+ * \internal
+ * \brief Adds diversion header information to an outbound SIP message
+ *
+ * \param tdata The outbound message
+ * \param data The redirecting data used to fill parts of the diversion header
+ */
+static void add_diversion_header(pjsip_tx_data *tdata, struct ast_party_redirecting *data)
+{
+	pjsip_fromto_hdr *hdr;
+	pjsip_name_addr *name_addr;
+	pjsip_sip_uri *uri;
+	pjsip_param *param;
+
+	struct ast_party_id *id = &data->from;
+	pjsip_uri *base = PJSIP_MSG_FROM_HDR(tdata->msg)->uri;
+
+	if (!id->number.valid || ast_strlen_zero(id->number.str)) {
+		return;
+	}
+
+	hdr = pjsip_from_hdr_create(tdata->pool);
+	hdr->type = PJSIP_H_OTHER;
+	pj_strdup(tdata->pool, &hdr->name, &diversion_name);
+	hdr->sname.slen = 0;
+
+	name_addr = pjsip_uri_clone(tdata->pool, base);
+	uri = pjsip_uri_get_uri(name_addr->uri);
+
+	pj_strdup2(tdata->pool, &name_addr->display, id->name.str);
+	pj_strdup2(tdata->pool, &uri->user, id->number.str);
+
+	param = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param);
+	param->name = pj_str("reason");
+	param->value = pj_str((char*)reason_code_to_str(&data->reason));
+	pj_list_insert_before(&hdr->other_param, param);
+
+	hdr->uri = (pjsip_uri *) name_addr;
+	pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr);
+}
+
+static void get_redirecting_add_diversion(struct ast_sip_session *session, pjsip_tx_data *tdata)
+{
+	struct ast_party_redirecting *data;
+
+	if (session->channel && session->endpoint->send_diversion &&
+	    (data = ast_channel_redirecting(session->channel))->count) {
+		add_diversion_header(tdata, data);
+	}
+}
+
+/*!
+ * \internal
+ * \brief Adds a diversion header to an outgoing INVITE request if
+ *  redirecting information is available.
+ *
+ * \param session The session on which the INVITE request is to be sent
+ * \param tdata The outbound INVITE request
+ */
+static void diversion_outgoing_request(struct ast_sip_session *session, pjsip_tx_data *tdata)
+{
+	get_redirecting_add_diversion(session, tdata);
+}
+
+/*!
+ * \internal
+ * \brief Adds a diversion header to an outgoing 3XX response
+ *
+ * \param session The session on which the INVITE response is to be sent
+ * \param tdata The outbound INVITE response
+ */
+static void diversion_outgoing_response(struct ast_sip_session *session, pjsip_tx_data *tdata)
+{
+	struct pjsip_status_line status = tdata->msg->line.status;
+
+	/* add to 302 and 181 */
+	if (PJSIP_IS_STATUS_IN_CLASS(status.code, 300) || (status.code == 181)) {
+		get_redirecting_add_diversion(session, tdata);
+	}
+}
+
+static struct ast_sip_session_supplement diversion_supplement = {
+	.method = "INVITE",
+	/* this supplement needs to be called after caller id
+           and after the channel has been created */
+	.priority = AST_SIP_SESSION_SUPPLEMENT_PRIORITY_CHANNEL + 100,
+	.incoming_request = diversion_incoming_request,
+	.incoming_response = diversion_incoming_response,
+	.outgoing_request = diversion_outgoing_request,
+	.outgoing_response = diversion_outgoing_response,
+};
+
+static int load_module(void)
+{
+	ast_sip_session_register_supplement(&diversion_supplement);
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	ast_sip_session_unregister_supplement(&diversion_supplement);
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP Add Diversion Header Support",
+		.load = load_module,
+		.unload = unload_module,
+		.load_pri = AST_MODPRI_APP_DEPEND,
+	       );
diff --git a/res/res_sip_dtmf_info.c b/res/res_sip_dtmf_info.c
index c8b03d5098..1954c695ea 100644
--- a/res/res_sip_dtmf_info.c
+++ b/res/res_sip_dtmf_info.c
@@ -46,8 +46,7 @@ static int dtmf_info_incoming_request(struct ast_sip_session *session, struct pj
 	char event = '\0';
 	unsigned int duration = 0;
 
-	if (pj_strcmp2(&body->content_type.type, "application") ||
-	    pj_strcmp2(&body->content_type.subtype, "dtmf-relay")) {
+	if (!ast_sip_is_content_type(&body->content_type, "application", "dtmf-relay")) {
 		return 0;
 	}
 
diff --git a/res/res_sip_endpoint_identifier_anonymous.c b/res/res_sip_endpoint_identifier_anonymous.c
new file mode 100644
index 0000000000..6f947e1a14
--- /dev/null
+++ b/res/res_sip_endpoint_identifier_anonymous.c
@@ -0,0 +1,125 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Mark Michelson <mmichelson@digium.com>
+ * Joshua Colp <jcolp@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>pjproject</depend>
+	<depend>res_sip</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/module.h"
+
+static int get_endpoint_details(pjsip_rx_data *rdata, char *domain, size_t domain_size)
+{
+	pjsip_uri *from = rdata->msg_info.from->uri;
+	pjsip_sip_uri *sip_from;
+	if (!PJSIP_URI_SCHEME_IS_SIP(from) && !PJSIP_URI_SCHEME_IS_SIPS(from)) {
+		return -1;
+	}
+	sip_from = (pjsip_sip_uri *) pjsip_uri_get_uri(from);
+	ast_copy_pj_str(domain, &sip_from->host, domain_size);
+	return 0;
+}
+
+static int find_transport_in_use(void *obj, void *arg, int flags)
+{
+	struct ast_sip_transport *transport = obj;
+	pjsip_rx_data *rdata = arg;
+
+	if ((transport->state->transport == rdata->tp_info.transport) ||
+		(transport->state->factory && !pj_strcmp(&transport->state->factory->addr_name.host, &rdata->tp_info.transport->local_name.host) &&
+			transport->state->factory->addr_name.port == rdata->tp_info.transport->local_name.port)) {
+		return CMP_MATCH | CMP_STOP;
+	}
+
+	return 0;
+}
+
+static struct ast_sip_endpoint *anonymous_identify(pjsip_rx_data *rdata)
+{
+	char domain_name[64], id[AST_UUID_STR_LEN];
+	struct ast_sip_endpoint *endpoint;
+	RAII_VAR(struct ast_sip_domain_alias *, alias, NULL, ao2_cleanup);
+	RAII_VAR(struct ao2_container *, transports, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
+
+	if (get_endpoint_details(rdata, domain_name, sizeof(domain_name))) {
+		return NULL;
+	}
+
+	/* Attempt to find the endpoint given the name and domain provided */
+	snprintf(id, sizeof(id), "anonymous@%s", domain_name);
+	if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) {
+		goto done;
+	}
+
+	/* See if an alias exists for the domain provided */
+	if ((alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain_name))) {
+		snprintf(id, sizeof(id), "anonymous@%s", alias->domain);
+		if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) {
+			goto done;
+		}
+	}
+
+	/* See if the transport this came in on has a provided domain */
+	if ((transports = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "transport", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL)) &&
+		(transport = ao2_callback(transports, 0, find_transport_in_use, rdata)) &&
+		!ast_strlen_zero(transport->domain)) {
+		snprintf(id, sizeof(id), "anonymous@%s", transport->domain);
+		if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) {
+			goto done;
+		}
+	}
+
+	/* Fall back to no domain */
+	endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", "anonymous");
+
+done:
+	if (endpoint) {
+		ast_debug(3, "Retrieved anonymous endpoint '%s'\n", ast_sorcery_object_get_id(endpoint));
+	}
+	return endpoint;
+}
+
+static struct ast_sip_endpoint_identifier anonymous_identifier = {
+	.identify_endpoint = anonymous_identify,
+};
+
+static int load_module(void)
+{
+	ast_sip_register_endpoint_identifier(&anonymous_identifier);
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	ast_sip_unregister_endpoint_identifier(&anonymous_identifier);
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP Anonymous endpoint identifier",
+		.load = load_module,
+		.unload = unload_module,
+		.load_pri = AST_MODPRI_DEFAULT,
+	       );
diff --git a/res/res_sip_exten_state.c b/res/res_sip_exten_state.c
new file mode 100644
index 0000000000..069343439f
--- /dev/null
+++ b/res/res_sip_exten_state.c
@@ -0,0 +1,620 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Kevin Harwell <kharwell@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>pjproject</depend>
+	<depend>res_sip</depend>
+	<depend>res_sip_pubsub</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_simple.h>
+#include <pjlib.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/res_sip_pubsub.h"
+#include "asterisk/res_sip_exten_state.h"
+#include "asterisk/module.h"
+#include "asterisk/logger.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/sorcery.h"
+#include "asterisk/app.h"
+
+#define BODY_SIZE 1024
+#define EVENT_TYPE_SIZE 50
+
+AST_RWLIST_HEAD_STATIC(providers, ast_sip_exten_state_provider);
+
+/*!
+ * \internal
+ * \brief Find a provider based on the given accept body type.
+ */
+static struct ast_sip_exten_state_provider *provider_by_type(const char *type)
+{
+	struct ast_sip_exten_state_provider *i;
+	SCOPED_LOCK(lock, &providers, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&providers, i, next) {
+		if (!strcmp(i->body_type, type)) {
+			return i;
+		}
+	}
+	AST_RWLIST_TRAVERSE_SAFE_END;
+	return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Find a provider based on the given accept body types.
+ */
+static struct ast_sip_exten_state_provider *provider_by_types(const char *event_name,
+							      char **types, int count)
+{
+	int i;
+	struct ast_sip_exten_state_provider *res;
+	for (i = 0; i < count; ++i) {
+		if ((res = provider_by_type(types[i])) &&
+		    !strcmp(event_name, res->event_name)) {
+			return res;
+		}
+	}
+	return NULL;
+}
+
+/*!
+ * \brief A subscription for extension state
+ *
+ * This structure acts as the owner for the underlying SIP subscription. It
+ * also keeps a pointer to an associated "provider" so when a state changes
+ * a notify data creator is quickly accessible.
+ */
+struct exten_state_subscription {
+	/*! Watcher id when registering for extension state changes */
+	int id;
+	/*! The SIP subscription */
+	struct ast_sip_subscription *sip_sub;
+	/*! The name of the event the subscribed to */
+	char event_name[EVENT_TYPE_SIZE];
+	/*! The number of body types */
+	int body_types_count;
+	/*! The subscription body types */
+	char **body_types;
+	/*! Context in which subscription looks for updates */
+	char context[AST_MAX_CONTEXT];
+	/*! Extension within the context to receive updates from */
+	char exten[AST_MAX_EXTENSION];
+	/*! The last known extension state */
+	enum ast_extension_states last_exten_state;
+};
+
+static void exten_state_subscription_destructor(void *obj)
+{
+	struct exten_state_subscription *sub = obj;
+	int i;
+
+	for (i = 0; i < sub->body_types_count; ++i) {
+		ast_free(sub->body_types[i]);
+	}
+
+	ast_free(sub->body_types);
+	ao2_cleanup(sub->sip_sub);
+}
+
+/*!
+ * \internal
+ * \brief Copies the body types the message wishes to subscribe to.
+ */
+static void copy_body_types(pjsip_rx_data *rdata,
+			    struct exten_state_subscription *exten_state_sub)
+{
+	int i;
+	pjsip_accept_hdr *hdr = (pjsip_accept_hdr*)
+		pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL);
+
+	exten_state_sub->body_types_count = hdr->count;
+	exten_state_sub->body_types = ast_malloc(hdr->count * sizeof(char*));
+
+	for (i = 0; i < hdr->count; ++i) {
+		exten_state_sub->body_types[i] =
+			ast_malloc(hdr->values[i].slen * sizeof(char*) + 1);
+
+		ast_copy_string(exten_state_sub->body_types[i],
+				pj_strbuf(&hdr->values[i]), hdr->values[i].slen + 1);
+	}
+}
+
+/*!
+ * \internal
+ * \brief Initialize the last extension state to something outside
+ * its usual states.
+ */
+#define INITIAL_LAST_EXTEN_STATE -3
+
+/*!
+ * \internal
+ * \brief Allocates an exten_state_subscription object.
+ *
+ * Creates the underlying SIP subscription for the given request. First makes
+ * sure that there are registered handler and provider objects available.
+ */
+static struct exten_state_subscription *exten_state_subscription_alloc(
+	struct ast_sip_endpoint *endpoint, enum ast_sip_subscription_role role, pjsip_rx_data *rdata)
+{
+	static const pj_str_t event_name = { "Event", 5 };
+	pjsip_event_hdr *hdr = (pjsip_event_hdr*)pjsip_msg_find_hdr_by_name(
+		rdata->msg_info.msg, &event_name, NULL);
+
+	struct ast_sip_exten_state_provider *provider;
+	RAII_VAR(struct exten_state_subscription *, exten_state_sub,
+		 ao2_alloc(sizeof(*exten_state_sub), exten_state_subscription_destructor), ao2_cleanup);
+
+	if (!exten_state_sub) {
+		return NULL;
+	}
+
+	ast_copy_pj_str(exten_state_sub->event_name, &hdr->event_type,
+			sizeof(exten_state_sub->event_name));
+
+	copy_body_types(rdata, exten_state_sub);
+	if (!(provider = provider_by_types(exten_state_sub->event_name,
+					   exten_state_sub->body_types,
+					   exten_state_sub->body_types_count))) {
+		ast_log(LOG_WARNING, "Unable to locate subscription handler\n");
+		return NULL;
+	}
+
+	if (!(exten_state_sub->sip_sub = ast_sip_create_subscription(
+		      provider->handler, role, endpoint, rdata))) {
+		ast_log(LOG_WARNING, "Unable to create SIP subscription for endpoint %s\n",
+			ast_sorcery_object_get_id(endpoint));
+		return NULL;
+	}
+
+	exten_state_sub->last_exten_state = INITIAL_LAST_EXTEN_STATE;
+
+	ao2_ref(exten_state_sub, +1);
+	return exten_state_sub;
+}
+
+/*!
+ * \internal
+ * \brief Create and send a NOTIFY request to the subscriber.
+ */
+static void create_send_notify(struct exten_state_subscription *exten_state_sub, const char *reason,
+			       pjsip_evsub_state evsub_state, struct ast_sip_exten_state_data *exten_state_data)
+{
+	RAII_VAR(struct ast_str *, body_text, ast_str_create(BODY_SIZE), ast_free_ptr);
+	pj_str_t reason_str;
+	const pj_str_t *reason_str_ptr = NULL;
+	pjsip_tx_data *tdata;
+	pjsip_dialog *dlg;
+	char local[PJSIP_MAX_URL_SIZE], remote[PJSIP_MAX_URL_SIZE];
+	struct ast_sip_body body;
+
+	struct ast_sip_exten_state_provider *provider = provider_by_types(
+		exten_state_sub->event_name, exten_state_sub->body_types,
+		exten_state_sub->body_types_count);
+
+	if (!provider) {
+		ast_log(LOG_ERROR, "Unable to locate provider for subscription\n");
+		return;
+	}
+
+	body.type = provider->type;
+	body.subtype = provider->subtype;
+
+	dlg = ast_sip_subscription_get_dlg(exten_state_sub->sip_sub);
+	ast_copy_pj_str(local, &dlg->local.info_str, sizeof(local));
+	ast_copy_pj_str(remote, &dlg->remote.info_str, sizeof(remote));
+
+	if (provider->create_body(exten_state_data, local, remote, &body_text)) {
+		ast_log(LOG_ERROR, "Unable to create body on NOTIFY request\n");
+		return;
+	}
+
+	body.body_text = ast_str_buffer(body_text);
+
+	if (reason) {
+		pj_cstr(&reason_str, reason);
+		reason_str_ptr = &reason_str;
+	}
+
+	if (pjsip_evsub_notify(ast_sip_subscription_get_evsub(exten_state_sub->sip_sub),
+			      evsub_state, NULL, reason_str_ptr, &tdata) != PJ_SUCCESS) {
+		ast_log(LOG_WARNING, "Unable to create NOTIFY request\n");
+		return;
+	}
+
+	if (ast_sip_add_body(tdata, &body)) {
+		ast_log(LOG_WARNING, "Unable to add body to NOTIFY request\n");
+		pjsip_tx_data_dec_ref(tdata);
+		return;
+	}
+
+	if (ast_sip_subscription_send_request(exten_state_sub->sip_sub, tdata) != PJ_SUCCESS) {
+		ast_log(LOG_WARNING, "Unable to send NOTIFY request\n");
+		pjsip_tx_data_dec_ref(tdata);
+	}
+}
+
+/*!
+ * \internal
+ * \brief Get device state information and send notification to the subscriber.
+ */
+static void send_notify(struct exten_state_subscription *exten_state_sub, const char *reason,
+	pjsip_evsub_state evsub_state)
+{
+	RAII_VAR(struct ao2_container*, info, NULL, ao2_cleanup);
+	char *subtype = NULL, *message = NULL;
+
+	struct ast_sip_exten_state_data exten_state_data = {
+		.exten = exten_state_sub->exten,
+		.presence_state = ast_hint_presence_state(NULL, exten_state_sub->context,
+							  exten_state_sub->exten, &subtype, &message),
+	};
+
+	if ((exten_state_data.exten_state = ast_extension_state_extended(
+		     NULL, exten_state_sub->context, exten_state_sub->exten, &info)) < 0) {
+
+		ast_log(LOG_WARNING, "Unable to get device hint/info for extension %s\n",
+			exten_state_sub->exten);
+		return;
+	}
+
+	exten_state_data.device_state_info = info;
+	create_send_notify(exten_state_sub, reason, evsub_state, &exten_state_data);
+}
+
+struct notify_task_data {
+	struct ast_sip_exten_state_data exten_state_data;
+	struct exten_state_subscription *exten_state_sub;
+	pjsip_evsub_state evsub_state;
+};
+
+static void notify_task_data_destructor(void *obj)
+{
+	struct notify_task_data *task_data = obj;
+
+	ao2_ref(task_data->exten_state_sub, -1);
+	ao2_cleanup(task_data->exten_state_data.device_state_info);
+}
+
+static struct notify_task_data *alloc_notify_task_data(char *exten, struct exten_state_subscription *exten_state_sub,
+						       struct ast_state_cb_info *info)
+{
+	struct notify_task_data *task_data =
+		ao2_alloc(sizeof(*task_data), notify_task_data_destructor);
+
+	if (!task_data) {
+		ast_log(LOG_WARNING, "Unable to create notify task data\n");
+		return NULL;
+	}
+
+	task_data->evsub_state = PJSIP_EVSUB_STATE_ACTIVE;
+	task_data->exten_state_sub = exten_state_sub;
+	task_data->exten_state_sub->last_exten_state = info->exten_state;
+	ao2_ref(task_data->exten_state_sub, +1);
+
+	task_data->exten_state_data.exten = exten_state_sub->exten;
+	task_data->exten_state_data.exten_state = info->exten_state;
+	task_data->exten_state_data.presence_state = info->presence_state;
+	task_data->exten_state_data.device_state_info = info->device_state_info;
+
+	if (task_data->exten_state_data.device_state_info) {
+		ao2_ref(task_data->exten_state_data.device_state_info, +1);
+	}
+
+	if ((info->exten_state == AST_EXTENSION_DEACTIVATED) ||
+	    (info->exten_state == AST_EXTENSION_REMOVED)) {
+		task_data->evsub_state = PJSIP_EVSUB_STATE_TERMINATED;
+		ast_log(LOG_WARNING, "Watcher for hint %s %s\n", exten, info->exten_state
+			 == AST_EXTENSION_REMOVED ? "removed" : "deactivated");
+	}
+
+	return task_data;
+}
+
+static int notify_task(void *obj)
+{
+	RAII_VAR(struct notify_task_data *, task_data, obj, ao2_cleanup);
+
+	create_send_notify(task_data->exten_state_sub, task_data->evsub_state ==
+			   PJSIP_EVSUB_STATE_TERMINATED ? "noresource" : NULL,
+			   task_data->evsub_state, &task_data->exten_state_data);
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Callback for exten/device state changes.
+ *
+ * Upon state change, send the appropriate notification to the subscriber.
+ */
+static int state_changed(char *context, char *exten,
+			 struct ast_state_cb_info *info, void *data)
+{
+	struct notify_task_data *task_data;
+	struct exten_state_subscription *exten_state_sub = data;
+
+	if (exten_state_sub->last_exten_state == info->exten_state) {
+		return 0;
+	}
+
+	if (!(task_data = alloc_notify_task_data(exten, exten_state_sub, info))) {
+		return -1;
+	}
+
+	/* safe to push this async since we copy the data from info and
+	   add a ref for the device state info */
+	if (ast_sip_push_task(ast_sip_subscription_get_serializer(task_data->exten_state_sub->sip_sub),
+			      notify_task, task_data)) {
+		ao2_cleanup(task_data);
+		return -1;
+	}
+	return 0;
+}
+
+static void state_changed_destroy(int id, void *data)
+{
+	struct exten_state_subscription *exten_state_sub = data;
+	ao2_cleanup(exten_state_sub);
+}
+
+static struct ast_datastore_info ds_info = { };
+static const char ds_name[] = "exten state datastore";
+
+/*!
+ * \internal
+ * \brief Add a datastore for exten exten_state_subscription.
+ *
+ * Adds the exten_state_subscription wrapper object to a datastore so it can be retrieved
+ * later based upon its association with the ast_sip_subscription.
+ */
+static int add_datastore(struct exten_state_subscription *exten_state_sub)
+{
+	RAII_VAR(struct ast_datastore *, datastore,
+		 ast_sip_subscription_alloc_datastore(&ds_info, ds_name), ao2_cleanup);
+
+	if (!datastore) {
+		return -1;
+	}
+
+	datastore->data = exten_state_sub;
+	ast_sip_subscription_add_datastore(exten_state_sub->sip_sub, datastore);
+	ao2_ref(exten_state_sub, +1);
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Get the exten_state_subscription object associated with the given
+ * ast_sip_subscription in the datastore.
+ */
+static struct exten_state_subscription *get_exten_state_sub(
+	struct ast_sip_subscription *sub)
+{
+	RAII_VAR(struct ast_datastore *, datastore,
+		 ast_sip_subscription_get_datastore(sub, ds_name), ao2_cleanup);
+
+	return datastore ? datastore->data : NULL;
+}
+
+static void subscription_shutdown(struct ast_sip_subscription *sub)
+{
+	struct exten_state_subscription *exten_state_sub = get_exten_state_sub(sub);
+
+	if (!exten_state_sub) {
+		return;
+	}
+
+	ast_extension_state_del(exten_state_sub->id, state_changed);
+	ast_sip_subscription_remove_datastore(exten_state_sub->sip_sub, ds_name);
+	/* remove data store reference */
+	ao2_cleanup(exten_state_sub);
+}
+
+static struct ast_sip_subscription *new_subscribe(struct ast_sip_endpoint *endpoint,
+						  pjsip_rx_data *rdata)
+{
+	pjsip_uri *uri = rdata->msg_info.msg->line.req.uri;
+	pjsip_sip_uri *sip_uri = pjsip_uri_get_uri(uri);
+	RAII_VAR(struct exten_state_subscription *, exten_state_sub, NULL, ao2_cleanup);
+
+	if (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri)) {
+		ast_log(LOG_WARNING, "Attempt to SUBSCRIBE to a non-SIP URI\n");
+		return NULL;
+	}
+
+	if (!(exten_state_sub = exten_state_subscription_alloc(endpoint, AST_SIP_NOTIFIER, rdata))) {
+		return NULL;
+	}
+
+	ast_copy_string(exten_state_sub->context, endpoint->context, sizeof(exten_state_sub->context));
+	ast_copy_pj_str(exten_state_sub->exten, &sip_uri->user, sizeof(exten_state_sub->exten));
+
+	if ((exten_state_sub->id = ast_extension_state_add_destroy_extended(
+		     exten_state_sub->context, exten_state_sub->exten,
+		     state_changed, state_changed_destroy, exten_state_sub)) < 0) {
+		ast_log(LOG_WARNING, "Unable to subscribe extension %s\n",
+			exten_state_sub->exten);
+		pjsip_evsub_terminate(ast_sip_subscription_get_evsub(exten_state_sub->sip_sub), PJ_FALSE);
+		return NULL;
+	}
+
+	/* bump the ref since ast_extension_state_add holds a reference */
+	ao2_ref(exten_state_sub, +1);
+
+	if (add_datastore(exten_state_sub)) {
+		ast_log(LOG_WARNING, "Unable to add to subscription datastore.\n");
+		pjsip_evsub_terminate(ast_sip_subscription_get_evsub(exten_state_sub->sip_sub), PJ_FALSE);
+		return NULL;
+	}
+
+	if (pjsip_evsub_accept(ast_sip_subscription_get_evsub(exten_state_sub->sip_sub),
+			       rdata, 200, NULL) != PJ_SUCCESS) {
+		ast_log(LOG_WARNING, "Unable to accept the incoming extension state subscription.\n");
+		pjsip_evsub_terminate(ast_sip_subscription_get_evsub(exten_state_sub->sip_sub), PJ_FALSE);
+		return NULL;
+	}
+
+	send_notify(exten_state_sub, NULL, PJSIP_EVSUB_STATE_ACTIVE);
+	return exten_state_sub->sip_sub;
+}
+
+static void resubscribe(struct ast_sip_subscription *sub, pjsip_rx_data *rdata,
+			struct ast_sip_subscription_response_data *response_data)
+{
+	struct exten_state_subscription *exten_state_sub = get_exten_state_sub(sub);
+
+	if (!exten_state_sub) {
+		return;
+	}
+
+	send_notify(exten_state_sub, NULL, PJSIP_EVSUB_STATE_ACTIVE);
+}
+
+static void subscription_timeout(struct ast_sip_subscription *sub)
+{
+	struct exten_state_subscription *exten_state_sub = get_exten_state_sub(sub);
+
+	if (!exten_state_sub) {
+		return;
+	}
+
+	ast_verbose(VERBOSE_PREFIX_3 "Subscription has timed out.\n");
+	send_notify(exten_state_sub, "timeout", PJSIP_EVSUB_STATE_TERMINATED);
+}
+
+static void subscription_terminated(struct ast_sip_subscription *sub,
+				    pjsip_rx_data *rdata)
+{
+	struct exten_state_subscription *exten_state_sub = get_exten_state_sub(sub);
+
+	if (!exten_state_sub) {
+		return;
+	}
+
+	ast_verbose(VERBOSE_PREFIX_3 "Subscription has been terminated.\n");
+	send_notify(exten_state_sub, NULL, PJSIP_EVSUB_STATE_TERMINATED);
+}
+
+/*!
+ * \internal
+ * \brief Create and register a subscription handler.
+ *
+ * Creates a subscription handler that can be registered with the pub/sub
+ * framework for the given event_name and accept value.
+ */
+static struct ast_sip_subscription_handler *create_and_register_handler(
+	const char *event_name, const char *accept)
+{
+	struct ast_sip_subscription_handler *handler =
+		ao2_alloc(sizeof(*handler), NULL);
+
+	if (!handler) {
+		return NULL;
+	}
+
+	handler->event_name = event_name;
+	handler->accept[0] = accept;
+
+	handler->subscription_shutdown = subscription_shutdown;
+	handler->new_subscribe = new_subscribe;
+	handler->resubscribe = resubscribe;
+	handler->subscription_timeout = subscription_timeout;
+	handler->subscription_terminated = subscription_terminated;
+
+	if (ast_sip_register_subscription_handler(handler)) {
+		ast_log(LOG_WARNING, "Unable to register subscription handler %s\n",
+			handler->event_name);
+		ao2_cleanup(handler);
+		return NULL;
+	}
+
+	return handler;
+}
+
+int ast_sip_register_exten_state_provider(struct ast_sip_exten_state_provider *obj)
+{
+	if (ast_strlen_zero(obj->type)) {
+		ast_log(LOG_WARNING, "Type not specified on provider for event %s\n",
+			obj->event_name);
+		return -1;
+	}
+
+	if (ast_strlen_zero(obj->subtype)) {
+		ast_log(LOG_WARNING, "Subtype not specified on provider for event %s\n",
+			obj->event_name);
+		return -1;
+	}
+
+	if (!obj->create_body) {
+		ast_log(LOG_WARNING, "Body handler not specified on provide for event %s\n",
+		    obj->event_name);
+		return -1;
+	}
+
+	if (!(obj->handler = create_and_register_handler(obj->event_name, obj->body_type))) {
+		ast_log(LOG_WARNING, "Handler could not be registered for provider event %s\n",
+		    obj->event_name);
+		return -1;
+	}
+
+	/* scope to avoid mix declarations */
+	{
+		SCOPED_LOCK(lock, &providers, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+		AST_RWLIST_INSERT_TAIL(&providers, obj, next);
+		ast_module_ref(ast_module_info->self);
+	}
+
+	return 0;
+}
+
+void ast_sip_unregister_exten_state_provider(struct ast_sip_exten_state_provider *obj)
+{
+	struct ast_sip_exten_state_provider *i;
+	SCOPED_LOCK(lock, &providers, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&providers, i, next) {
+		if (i == obj) {
+			ast_sip_unregister_subscription_handler(i->handler);
+			ao2_cleanup(i->handler);
+			AST_RWLIST_REMOVE_CURRENT(next);
+			ast_module_unref(ast_module_info->self);
+			break;
+		}
+	}
+	AST_RWLIST_TRAVERSE_SAFE_END;
+}
+
+static int load_module(void)
+{
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "SIP Extension State Notifications",
+		.load = load_module,
+		.unload = unload_module,
+		.load_pri = AST_MODPRI_CHANNEL_DEPEND,
+);
diff --git a/res/res_sip_exten_state.exports.in b/res/res_sip_exten_state.exports.in
new file mode 100644
index 0000000000..0cce6f6dda
--- /dev/null
+++ b/res/res_sip_exten_state.exports.in
@@ -0,0 +1,7 @@
+{
+	global:
+		LINKER_SYMBOL_PREFIXast_sip_register_exten_state_provider;
+		LINKER_SYMBOL_PREFIXast_sip_unregister_exten_state_provider;
+	local:
+		*;
+};
diff --git a/res/res_sip_messaging.c b/res/res_sip_messaging.c
new file mode 100644
index 0000000000..10d47047f1
--- /dev/null
+++ b/res/res_sip_messaging.c
@@ -0,0 +1,660 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Kevin Harwell <kharwell@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>pjproject</depend>
+	<depend>res_sip</depend>
+	<depend>res_sip_session</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "pjsua-lib/pjsua.h"
+
+#include "asterisk/message.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/res_sip.h"
+#include "asterisk/res_sip_session.h"
+
+const pjsip_method pjsip_message_method = {PJSIP_OTHER_METHOD, {"MESSAGE", 7} };
+
+#define MAX_HDR_SIZE 512
+#define MAX_BODY_SIZE 1024
+#define MAX_EXTEN_SIZE 256
+#define MAX_USER_SIZE 128
+
+/*!
+ * \internal
+ * \brief Determine where in the dialplan a call should go
+ *
+ * \details This uses the username in the request URI to try to match
+ * an extension in an endpoint's context in order to route the call.
+ *
+ * \param rdata The SIP request
+ * \param context The context to use
+ * \param exten The extension to use
+ */
+static enum pjsip_status_code get_destination(const pjsip_rx_data *rdata, const char *context, char *exten)
+{
+	pjsip_uri *ruri = rdata->msg_info.msg->line.req.uri;
+	pjsip_sip_uri *sip_ruri;
+
+	if (!PJSIP_URI_SCHEME_IS_SIP(ruri) && !PJSIP_URI_SCHEME_IS_SIPS(ruri)) {
+		return PJSIP_SC_UNSUPPORTED_URI_SCHEME;
+	}
+
+	sip_ruri = pjsip_uri_get_uri(ruri);
+	ast_copy_pj_str(exten, &sip_ruri->user, MAX_EXTEN_SIZE);
+
+	if (ast_exists_extension(NULL, context, exten, 1, NULL)) {
+		return PJSIP_SC_OK;
+	}
+	return PJSIP_SC_NOT_FOUND;
+}
+
+/*!
+ * \internal
+ * \brief Checks to make sure the request has the correct content type.
+ *
+ * \details This module supports the following media types: "text/plain".
+ * Return unsupported otherwise.
+ *
+ * \param rdata The SIP request
+ */
+static enum pjsip_status_code check_content_type(const pjsip_rx_data *rdata)
+{
+	if (ast_sip_is_content_type(&rdata->msg_info.msg->body->content_type,
+				    "text",
+				    "plain")) {
+		return PJSIP_SC_OK;
+	} else {
+		return PJSIP_SC_UNSUPPORTED_MEDIA_TYPE;
+	}
+}
+
+/*!
+ * \internal
+ * \brief Puts pointer past 'sip[s]:' string that should be at the
+ * front of the given 'fromto' parameter
+ *
+ * \param fromto 'From' or 'To' field containing 'sip:'
+ */
+static const char* skip_sip(const char *fromto)
+{
+	const char *p;
+
+	/* need to be one past 'sip:' or 'sips:' */
+	if (!(p = strstr(fromto, "sip"))) {
+		return fromto;
+	}
+
+	p += 3;
+	if (*p == 's') {
+		++p;
+	}
+	return ++p;
+}
+
+/*!
+ * \internal
+ * \brief Retrieves an endpoint if specified in the given 'fromto'
+ *
+ * Expects the given 'fromto' to be in one of the following formats:
+ *      sip[s]:endpoint[/aor]
+ *      sip[s]:endpoint[/uri]
+ *
+ * If an optional aor is given it will try to find an associated uri
+ * to return.  If an optional uri is given then that will be returned,
+ * otherwise uri will be NULL.
+ *
+ * \param fromto 'From' or 'To' field with possible endpoint
+ * \param uri Optional uri to return
+ */
+static struct ast_sip_endpoint* get_endpoint(const char *fromto, char **uri)
+{
+	const char *name = skip_sip(fromto);
+	struct ast_sip_endpoint* endpoint;
+	struct ast_sip_aor *aor;
+
+	if ((*uri = strchr(name, '/'))) {
+		*(*uri)++ = '\0';
+	}
+
+	/* endpoint is required */
+	if (ast_strlen_zero(name)) {
+		return NULL;
+	}
+
+	if (!(endpoint = ast_sorcery_retrieve_by_id(
+		      ast_sip_get_sorcery(), "endpoint", name))) {
+		return NULL;
+	}
+
+	if (*uri && (aor = ast_sip_location_retrieve_aor(*uri))) {
+		*uri = (char*)ast_sip_location_retrieve_first_aor_contact(aor)->uri;
+	}
+
+	return endpoint;
+}
+
+/*!
+ * \internal
+ * \brief Updates fields in an outgoing 'From' header.
+ *
+ * \param tdata The outgoing message data structure
+ * \param from Info to potentially copy into the 'From' header
+ */
+static void update_from(pjsip_tx_data *tdata, const char *from)
+{
+	/* static const pj_str_t hname = { "From", 4 }; */
+	pjsip_name_addr *from_name_addr;
+	pjsip_sip_uri *from_uri;
+	pjsip_uri *parsed;
+	char *uri;
+
+	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
+
+	if (ast_strlen_zero(from)) {
+		return;
+	}
+
+	if (!(endpoint = get_endpoint(from, &uri))) {
+		return;
+	}
+
+	if (ast_strlen_zero(uri)) {
+		/* if no aor/uri was specified get one from the endpoint */
+		uri = (char*)ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors)->uri;
+	}
+
+	/* get current 'from' hdr & uri - going to overwrite some fields */
+	from_name_addr = (pjsip_name_addr *)PJSIP_MSG_FROM_HDR(tdata->msg)->uri;
+	from_uri = pjsip_uri_get_uri(from_name_addr);
+
+	/* check to see if uri is in 'name <sip:user@domain>' format */
+	if ((parsed = pjsip_parse_uri(tdata->pool, uri, strlen(uri), PJSIP_PARSE_URI_AS_NAMEADDR))) {
+		pjsip_name_addr *name_addr = (pjsip_name_addr *)parsed;
+		pjsip_sip_uri *sip_uri = pjsip_uri_get_uri(name_addr->uri);
+
+		pj_strdup(tdata->pool, &from_name_addr->display, &name_addr->display);
+		pj_strdup(tdata->pool, &from_uri->user, &sip_uri->user);
+		pj_strdup(tdata->pool, &from_uri->host, &sip_uri->host);
+		from_uri->port = sip_uri->port;
+	} else {
+		/* assume it is 'user[@domain]' format */
+		char *domain = strchr(uri, '@');
+		if (domain) {
+			*domain++ = '\0';
+			pj_strdup2(tdata->pool, &from_uri->host, domain);
+		}
+		pj_strdup2(tdata->pool, &from_uri->user, uri);
+	}
+}
+
+/*!
+ * \internal
+ * \brief Checks if the given msg var name should be blocked.
+ *
+ * \details Some headers are not allowed to be overriden by the user.
+ *  Determine if the given var header name from the user is blocked for
+ *  an outgoing MESSAGE.
+ *
+ * \param name name of header to see if it is blocked.
+ *
+ * \retval TRUE if the given header is blocked.
+ */
+static int is_msg_var_blocked(const char *name)
+{
+	int i;
+
+	/*
+	 * Don't block Content-Type or Max-Forwards headers because the
+	 * user can override them.
+	 */
+	static const char *hdr[] = {
+		"To",
+		"From",
+		"Via",
+		"Route",
+		"Contact",
+		"Call-ID",
+		"CSeq",
+		"Allow",
+		"Content-Length",
+		"Request-URI",
+	};
+
+	for (i = 0; i < ARRAY_LEN(hdr); ++i) {
+		if (!strcasecmp(name, hdr[i])) {
+			/* Block addition of this header. */
+			return 1;
+		}
+	}
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Copies any other msg vars over to the request headers.
+ *
+ * \param msg The msg structure to copy headers from
+ * \param tdata The SIP transmission data
+ */
+static enum pjsip_status_code vars_to_headers(const struct ast_msg *msg, pjsip_tx_data *tdata)
+{
+	const char *name;
+	const char *value;
+	int max_forwards;
+
+	RAII_VAR(struct ast_msg_var_iterator *, i, ast_msg_var_iterator_init(msg), ast_msg_var_iterator_destroy);
+	while (ast_msg_var_iterator_next(msg, i, &name, &value)) {
+		if (!strcasecmp(name, "Max-Forwards")) {
+			/* Decrement Max-Forwards for SIP loop prevention. */
+			if (sscanf(value, "%30d", &max_forwards) != 1 || --max_forwards == 0) {
+				ast_log(LOG_NOTICE, "MESSAGE(Max-Forwards) reached zero.  MESSAGE not sent.\n");
+				return -1;
+			}
+			sprintf((char*)value, "%d", max_forwards);
+			ast_sip_add_header(tdata, name, value);
+		}
+		else if (!is_msg_var_blocked(name)) {
+			ast_sip_add_header(tdata, name, value);
+		}
+		ast_msg_var_unref_current(i);
+	}
+	return PJSIP_SC_OK;
+}
+
+/*!
+ * \internal  
+ * \brief Copies any other request header data over to ast_msg structure.
+ *
+ * \param rdata The SIP request
+ * \param msg The msg structure to copy headers into
+ */
+static int headers_to_vars(const pjsip_rx_data *rdata, struct ast_msg *msg)
+{
+	char *c;
+	char buf[MAX_HDR_SIZE];
+	int res = 0;
+	pjsip_hdr *h = rdata->msg_info.msg->hdr.next;
+	pjsip_hdr *end= &rdata->msg_info.msg->hdr;
+
+	while (h != end) {
+		if ((res = pjsip_hdr_print_on(h, buf, sizeof(buf)-1)) > 0) {
+			buf[res] = '\0';
+			if ((c = strchr(buf, ':'))) {
+				ast_copy_string(buf, ast_skip_blanks(c + 1), sizeof(buf)-(c-buf));
+			}
+
+			if ((res = ast_msg_set_var(msg, pj_strbuf(&h->name), buf)) != 0) {
+				break;
+			}
+		}
+		h = h->next;
+	}
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Prints the message body into the given char buffer.
+ *
+ * \details Copies body content from the received data into the given
+ * character buffer removing any extra carriage return/line feeds.
+ *
+ * \param rdata The SIP request
+ * \param buf Buffer to fill
+ * \param len The length of the buffer
+ */
+static int print_body(pjsip_rx_data *rdata, char *buf, int len)
+{
+	int res = rdata->msg_info.msg->body->print_body(
+		rdata->msg_info.msg->body, buf, len);
+
+	if (res < 0) {
+		return res;
+	}
+
+	/* remove any trailing carriage return/line feeds */
+	while (res > 0 && ((buf[--res] == '\r') || (buf[res] == '\n')));
+
+	buf[++res] = '\0';
+
+	return res;
+}
+
+/*!
+ * \internal
+ * \brief Converts a pjsip_rx_data structure to an ast_msg structure.
+ *
+ * \details Attempts to fill in as much information as possible into the given
+ * msg structure copied from the given request data.
+ *
+ * \param rdata The SIP request
+ * \param msg The asterisk message structure to fill in.
+ */
+static enum pjsip_status_code rx_data_to_ast_msg(pjsip_rx_data *rdata, struct ast_msg *msg)
+{
+
+#define CHECK_RES(z_) do { if (z_) { ast_msg_destroy(msg); \
+		return PJSIP_SC_INTERNAL_SERVER_ERROR; } } while (0)
+
+	int size;
+	char buf[MAX_BODY_SIZE];
+	pjsip_name_addr *name_addr;
+	const char *field;
+	pjsip_status_code code;
+	struct ast_sip_endpoint *endpt = ast_pjsip_rdata_get_endpoint(rdata);
+
+	/* make sure there is an appropriate context and extension*/
+	if ((code = get_destination(rdata, endpt->context, buf)) != PJSIP_SC_OK) {
+		return code;
+	}
+
+	CHECK_RES(ast_msg_set_context(msg, "%s", endpt->context));
+	CHECK_RES(ast_msg_set_exten(msg, "%s", buf));
+
+	/* to header */
+	name_addr = (pjsip_name_addr *)rdata->msg_info.to->uri;
+	if ((size = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, name_addr, buf, sizeof(buf)-1)) > 0) {
+		buf[size] = '\0';
+		CHECK_RES(ast_msg_set_to(msg, "%s", buf));
+	}
+
+	/* from header */
+	name_addr = (pjsip_name_addr *)rdata->msg_info.from->uri;
+	if ((size = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, name_addr, buf, sizeof(buf)-1)) > 0) {
+		buf[size] = '\0';
+		CHECK_RES(ast_msg_set_from(msg, "%s", buf));
+	}
+
+	/* contact header */
+	if ((size = pjsip_hdr_print_on(pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, NULL), buf, sizeof(buf)-1)) > 0) {
+		buf[size] = '\0';
+		CHECK_RES(ast_msg_set_var(msg, "SIP_FULLCONTACT", buf));
+	}
+
+	/* receive address */
+	field = pj_sockaddr_print(&rdata->pkt_info.src_addr, buf, sizeof(buf)-1, 1);
+	CHECK_RES(ast_msg_set_var(msg, "SIP_RECVADDR", field));
+
+	/* body */
+	if (print_body(rdata, buf, sizeof(buf) - 1) > 0) {
+		CHECK_RES(ast_msg_set_body(msg, "%s", buf));
+	}
+
+	/* endpoint name */
+	if (endpt->id.name.valid) {
+		CHECK_RES(ast_msg_set_var(msg, "SIP_PEERNAME", endpt->id.name.str));
+	}
+
+	CHECK_RES(headers_to_vars(rdata, msg));
+
+	return PJSIP_SC_OK;
+}
+
+struct msg_data {
+	struct ast_msg *msg;
+        char *to;
+	char *from;
+};
+
+static void msg_data_destroy(void *obj)
+{
+	struct msg_data *mdata = obj;
+
+	ast_free(mdata->from);
+	ast_free(mdata->to);
+
+	ast_msg_destroy(mdata->msg);
+}
+
+static struct msg_data* msg_data_create(const struct ast_msg *msg, const char *to, const char *from)
+{
+	char *tag;
+	struct msg_data *mdata = ao2_alloc(sizeof(*mdata), msg_data_destroy);
+
+	if (!mdata) {
+		return NULL;
+	}
+
+	/* typecast to suppress const warning */
+	mdata->msg = ast_msg_ref((struct ast_msg*)msg);
+
+	mdata->to = ast_strdup(to);
+	mdata->from = ast_strdup(from);
+
+	/* sometimes from can still contain the tag at this point, so remove it */
+	if ((tag = strchr(mdata->from, ';'))) {
+		*tag = '\0';
+	}
+
+	return mdata;
+}
+
+static int msg_send(void *data)
+{
+	RAII_VAR(struct msg_data *, mdata, data, ao2_cleanup);
+
+	const struct ast_sip_body body = {
+		.type = "text",
+		.subtype = "plain",
+		.body_text = ast_msg_get_body(mdata->msg)
+	};
+
+	pjsip_tx_data *tdata;
+	char *uri;
+
+	RAII_VAR(struct ast_sip_endpoint *, endpoint, get_endpoint(
+			 mdata->to, &uri), ao2_cleanup);
+	if (!endpoint) {
+		ast_log(LOG_ERROR, "SIP MESSAGE - Endpoint not found in %s\n", mdata->to);
+		return -1;
+	}
+
+	if (ast_sip_create_request("MESSAGE", NULL, endpoint, uri, &tdata)) {
+		ast_log(LOG_ERROR, "SIP MESSAGE - Could not create request\n");
+		return -1;
+	}
+
+	if (ast_sip_add_body(tdata, &body)) {
+		pjsip_tx_data_dec_ref(tdata);
+		ast_log(LOG_ERROR, "SIP MESSAGE - Could not add body to request\n");
+		return -1;
+	}
+
+	update_from(tdata, mdata->from);
+	vars_to_headers(mdata->msg, tdata);
+	if (ast_sip_send_request(tdata, NULL, endpoint)) {
+		pjsip_tx_data_dec_ref(tdata);
+		ast_log(LOG_ERROR, "SIP MESSAGE - Could not send request\n");
+		return -1;
+	}
+
+	return PJ_SUCCESS;
+}
+
+static int sip_msg_send(const struct ast_msg *msg, const char *to, const char *from)
+{
+	struct msg_data *mdata;
+
+	if (ast_strlen_zero(to)) {
+		ast_log(LOG_ERROR, "SIP MESSAGE - a 'To' URI  must be specified\n");
+		return -1;
+	}
+
+	if (!(mdata = msg_data_create(msg, to, from)) ||
+	    ast_sip_push_task(NULL, msg_send, mdata)) {
+		ao2_ref(mdata, -1);
+		return -1;
+	}
+	return 0;
+}
+
+static const struct ast_msg_tech msg_tech = {
+	.name = "sip",
+	.msg_send = sip_msg_send,
+};
+
+static pj_status_t send_response(pjsip_rx_data *rdata, enum pjsip_status_code code,
+				 pjsip_dialog *dlg, pjsip_transaction *tsx)
+{
+	pjsip_tx_data *tdata;
+	pj_status_t status;
+	pjsip_response_addr res_addr;
+
+	pjsip_endpoint *endpt = ast_sip_get_pjsip_endpoint();
+
+	status = pjsip_endpt_create_response(endpt, rdata, code, NULL, &tdata);
+	if (status != PJ_SUCCESS) {
+		ast_log(LOG_ERROR, "Unable to create response (%d)\n", status);
+		return status;
+	}
+
+	if (dlg && tsx) {
+		status = pjsip_dlg_send_response(dlg, tsx, tdata);
+	} else {
+		/* Get where to send request. */
+		status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
+		if (status != PJ_SUCCESS) {
+			ast_log(LOG_ERROR, "Unable to get response address (%d)\n", status);
+			return status;
+		}
+		status = pjsip_endpt_send_response(endpt, &res_addr, tdata, NULL, NULL);
+	}
+
+	if (status != PJ_SUCCESS) {
+		ast_log(LOG_ERROR, "Unable to send response (%d)\n", status);
+	}
+
+	return status;
+}
+
+static pj_bool_t module_on_rx_request(pjsip_rx_data *rdata)
+{
+	enum pjsip_status_code code;
+	struct ast_msg *msg;
+
+	/* if not a MESSAGE, don't handle */
+	if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_message_method)) {
+		return PJ_FALSE;
+	}
+
+	msg = ast_msg_alloc();
+	if (!msg) {
+		send_response(rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL);
+		return PJ_TRUE;
+	}
+
+	if ((code = check_content_type(rdata)) != PJSIP_SC_OK) {
+		send_response(rdata, code, NULL, NULL);
+		return PJ_TRUE;
+	}
+
+	if ((code = rx_data_to_ast_msg(rdata, msg)) == PJSIP_SC_OK) {
+		/* send it to the dialplan */
+		ast_msg_queue(msg);
+		code = PJSIP_SC_ACCEPTED;
+	}
+
+	send_response(rdata, code, NULL, NULL);
+	return PJ_TRUE;
+}
+
+static int incoming_in_dialog_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
+{
+	char buf[MAX_BODY_SIZE];
+	enum pjsip_status_code code;
+	struct ast_frame f;
+
+	pjsip_dialog *dlg = session->inv_session->dlg;
+	pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata);
+
+	if ((code = check_content_type(rdata)) != PJSIP_SC_OK) {
+		send_response(rdata, code, dlg, tsx);
+		return 0;
+	}
+
+	if (print_body(rdata, buf, sizeof(buf)-1) < 1) {
+		/* invalid body size */
+		return 0;
+	}
+
+	memset(&f, 0, sizeof(f));
+	f.frametype = AST_FRAME_TEXT;
+	f.subclass.integer = 0;
+	f.offset = 0;
+	f.data.ptr = buf;
+	f.datalen = strlen(buf) + 1;
+	ast_queue_frame(session->channel, &f);
+
+	send_response(rdata, PJSIP_SC_ACCEPTED, dlg, tsx);
+	return 0;
+}
+
+static struct ast_sip_session_supplement messaging_supplement = {
+	.method = "MESSAGE",
+	.incoming_request = incoming_in_dialog_request
+};
+
+static pjsip_module messaging_module = {
+	.name = {"Messaging Module", 16},
+	.id = -1,
+	.priority = PJSIP_MOD_PRIORITY_APPLICATION,
+	.on_rx_request = module_on_rx_request,
+};
+
+static int load_module(void)
+{
+	if (ast_sip_register_service(&messaging_module) != PJ_SUCCESS) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	if (pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(),
+				       NULL, PJSIP_H_ALLOW, NULL, 1,
+				       &pjsip_message_method.name) != PJ_SUCCESS) {
+
+		ast_sip_unregister_service(&messaging_module);
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	if (ast_msg_tech_register(&msg_tech)) {
+		ast_sip_unregister_service(&messaging_module);
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	ast_sip_session_register_supplement(&messaging_supplement);
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	ast_sip_session_unregister_supplement(&messaging_supplement);
+	ast_msg_tech_unregister(&msg_tech);
+	ast_sip_unregister_service(&messaging_module);
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP Messaging Support",
+		.load = load_module,
+		.unload = unload_module,
+		.load_pri = AST_MODPRI_APP_DEPEND,
+	       );
diff --git a/res/res_sip_one_touch_record_info.c b/res/res_sip_one_touch_record_info.c
new file mode 100644
index 0000000000..b574b304c2
--- /dev/null
+++ b/res/res_sip_one_touch_record_info.c
@@ -0,0 +1,118 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, malleable, llc.
+ *
+ * Sean Bright <sean@malleable.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	 <depend>pjproject</depend>
+	 <depend>res_sip</depend>
+	 <depend>res_sip_session</depend>
+	 <support_level>core</support_level>
+***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_ua.h>
+
+#include "asterisk/features.h"
+#include "asterisk/res_sip.h"
+#include "asterisk/res_sip_session.h"
+#include "asterisk/module.h"
+#include "asterisk/features_config.h"
+
+static void send_response(struct ast_sip_session *session, int code, struct pjsip_rx_data *rdata)
+{
+	pjsip_tx_data *tdata;
+
+	if (pjsip_dlg_create_response(session->inv_session->dlg, rdata, code, NULL, &tdata) == PJ_SUCCESS) {
+		struct pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata);
+
+		pjsip_dlg_send_response(session->inv_session->dlg, tsx, tdata);
+	}
+}
+
+static int handle_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
+{
+	static const pj_str_t rec_str = { "Record", 6 };
+	pjsip_generic_string_hdr *record;
+	int feature_res;
+	char feature_code[AST_FEATURE_MAX_LEN];
+	char *digit;
+
+	record = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &rec_str, NULL);
+
+	/* If we don't have Record header, we have nothing to do */
+	if (!record || (pj_stricmp2(&record->hvalue, "on") && pj_stricmp2(&record->hvalue, "off"))) {
+		return 0;
+	}
+
+	if (!session->channel) {
+		send_response(session, 481, rdata);
+		return 0;
+	}
+
+	/* Is this endpoint configured with One Touch Recording? */
+	if (!session->endpoint->one_touch_recording) {
+		send_response(session, 403, rdata);
+		return 0;
+	}
+
+	ast_channel_lock(session->channel);
+	feature_res = ast_get_builtin_feature(session->channel, "automixmon", feature_code, sizeof(feature_code));
+	ast_channel_unlock(session->channel);
+
+	if (feature_res || ast_strlen_zero(feature_code)) {
+		send_response(session, 403, rdata);
+		return 0;
+	}
+
+	for (digit = feature_code; *digit; ++digit) {
+		struct ast_frame f = { AST_FRAME_DTMF, .subclass.integer = *digit, .len = 100 };
+		ast_queue_frame(session->channel, &f);
+	}
+
+	send_response(session, 200, rdata);
+
+	return 0;
+}
+
+static struct ast_sip_session_supplement info_supplement = {
+	.method = "INFO",
+	.incoming_request = handle_incoming_request,
+};
+
+static int load_module(void)
+{
+	if (ast_sip_session_register_supplement(&info_supplement)) {
+		ast_log(LOG_ERROR, "Unable to register One Touch Recording supplement\n");
+		return AST_MODULE_LOAD_FAILURE;
+	}
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	ast_sip_session_unregister_supplement(&info_supplement);
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP INFO One Touch Recording Support",
+	.load = load_module,
+	.unload = unload_module,
+	.load_pri = AST_MODPRI_APP_DEPEND,
+	);
diff --git a/res/res_sip_outbound_registration.c b/res/res_sip_outbound_registration.c
index 9d73f37d55..203ecfc5a8 100644
--- a/res/res_sip_outbound_registration.c
+++ b/res/res_sip_outbound_registration.c
@@ -600,10 +600,10 @@ static int sip_outbound_registration_apply(const struct ast_sorcery *sorcery, vo
 			return -1;
 		}
 
-		if (transport->type == AST_TRANSPORT_UDP) {
+		if (transport->state->transport) {
 			selector.type = PJSIP_TPSELECTOR_TRANSPORT;
 			selector.u.transport = transport->state->transport;
-		} else if (transport->type == AST_TRANSPORT_TCP || transport->type == AST_TRANSPORT_TLS) {
+		} else if (transport->state->factory) {
 			selector.type = PJSIP_TPSELECTOR_LISTENER;
 			selector.u.listener = transport->state->factory;
 		} else {
diff --git a/res/res_sip_pidf.c b/res/res_sip_pidf.c
new file mode 100644
index 0000000000..78633da9a5
--- /dev/null
+++ b/res/res_sip_pidf.c
@@ -0,0 +1,341 @@
+/*
+ * asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Kevin Harwell <kharwell@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>pjproject</depend>
+	<depend>res_sip</depend>
+	<depend>res_sip_pubsub</depend>
+	<depend>res_sip_exten_state</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_simple.h>
+#include <pjlib.h>
+
+#include "asterisk/module.h"
+#include "asterisk/res_sip.h"
+#include "asterisk/res_sip_exten_state.h"
+
+enum state {
+	NOTIFY_OPEN,
+	NOTIFY_INUSE,
+	NOTIFY_CLOSED
+};
+
+static void exten_state_to_str(int state, char **statestring, char **pidfstate,
+			       char **pidfnote, int *local_state)
+{
+	switch (state) {
+	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 */
+		*statestring = "terminated";
+		*local_state = NOTIFY_OPEN;
+		*pidfstate = "--";
+		*pidfnote ="Ready";
+
+		break;
+	}
+}
+
+static pj_xml_attr *create_attr(pj_pool_t *pool, pj_xml_node *node,
+				const char *name, const char *value)
+{
+	pj_xml_attr *attr = PJ_POOL_ALLOC_T(pool, pj_xml_attr);
+
+	pj_strdup2(pool, &attr->name, name);
+	pj_strdup2(pool, &attr->value, value);
+
+	pj_xml_add_attr(node, attr);
+	return attr;
+}
+
+static pj_xml_node *create_node(pj_pool_t *pool, pj_xml_node *parent,
+				const char* name)
+{
+	pj_xml_node *node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
+
+	pj_list_init(&node->attr_head);
+	pj_list_init(&node->node_head);
+
+	pj_strdup2(pool, &node->name, name);
+
+	node->content.ptr = NULL;
+	node->content.slen = 0;
+
+	pj_xml_add_node(parent, node);
+	return node;
+}
+
+static pj_xml_attr *find_node_attr(pj_pool_t* pool, pj_xml_node *parent,
+				   const char *node_name, const char *attr_name)
+{
+	pj_str_t name;
+	pj_xml_node *node;
+	pj_xml_attr *attr;
+
+	if (!(node = pj_xml_find_node(parent, pj_cstr(&name, node_name)))) {
+		node = create_node(pool, parent, node_name);
+	}
+
+	if (!(attr = pj_xml_find_attr(node, pj_cstr(&name, attr_name), NULL))) {
+		attr = create_attr(pool, node, attr_name, "");
+	}
+
+	return attr;
+}
+
+/*!
+ * \internal
+ * \brief Adds non standard elements to the xml body
+ *
+ * This is some code that was part of the original chan_sip implementation
+ * that is not part of the RFC 3863 definition, but we are keeping available
+ * for backward compatability. The original comment stated that Eyebeam
+ * supports this format.
+
+ */
+static void add_non_standard(pj_pool_t *pool, pj_xml_node *node, const char *pidfstate)
+{
+	static const char *XMLNS_PP = "xmlns:pp";
+	static const char *XMLNS_PERSON = "urn:ietf:params:xml:ns:pidf:person";
+
+	static const char *XMLNS_ES = "xmlns:es";
+	static const char *XMLNS_RPID_STATUS = "urn:ietf:params:xml:ns:pidf:rpid:status:rpid-status";
+
+	static const char *XMLNS_EP = "xmlns:ep";
+	static const char *XMLNS_RPID_PERSON = "urn:ietf:params:xml:ns:pidf:rpid:rpid-person";
+
+	pj_xml_node *person = create_node(pool, node, "pp:person");
+	pj_xml_node *status = create_node(pool, person, "status");
+
+	if (pidfstate[0] != '-') {
+		pj_xml_node *activities = create_node(pool, status, "ep:activities");
+		pj_strdup2(pool, &activities->content, "ep:");
+		pj_strcat2(&activities->content, pidfstate);
+	}
+
+	create_attr(pool, node, XMLNS_PP, XMLNS_PERSON);
+	create_attr(pool, node, XMLNS_ES, XMLNS_RPID_STATUS);
+	create_attr(pool, node, XMLNS_EP, XMLNS_RPID_PERSON);
+}
+
+static void release_pool(void *obj)
+{
+	pj_pool_t *pool = obj;
+
+	pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
+}
+
+static int pidf_xml_create_body(struct ast_sip_exten_state_data *data, const char *local,
+				const char *remote, struct ast_str **body_text)
+{
+	pjpidf_pres *pres;
+	pjpidf_tuple *tuple;
+	pj_str_t entity, note, id, contact, priority;
+	char *statestring = NULL, *pidfstate = NULL, *pidfnote = NULL;
+	int local_state, size;
+
+	RAII_VAR(pj_pool_t *, pool,
+		 pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(),
+					 "pidf", 1024, 1024), release_pool);
+
+	exten_state_to_str(data->exten_state, &statestring, &pidfstate,
+			   &pidfnote, &local_state);
+
+	if (!(pres = pjpidf_create(pool, pj_cstr(&entity, local)))) {
+		ast_log(LOG_WARNING, "Unable to create PIDF presence\n");
+		return -1;
+	}
+
+	add_non_standard(pool, pres, pidfstate);
+
+	if (!pjpidf_pres_add_note(pool, pres, pj_cstr(&note, pidfnote))) {
+		ast_log(LOG_WARNING, "Unable to add note to PIDF presence\n");
+		return -1;
+	}
+
+	if (!(tuple = pjpidf_pres_add_tuple(pool, pres, pj_cstr(&id, data->exten)))) {
+		ast_log(LOG_WARNING, "Unable to create PIDF tuple\n");
+		return -1;
+	}
+
+	pjpidf_tuple_set_contact(pool, tuple, pj_cstr(&contact, remote));
+	pjpidf_tuple_set_contact_prio(pool, tuple, pj_cstr(&priority, "1"));
+	pjpidf_status_set_basic_open(pjpidf_tuple_get_status(tuple),
+				     (pidfstate[0] == 'b') || (local_state != NOTIFY_CLOSED));
+
+	if (!(size = pjpidf_print(pres, ast_str_buffer(*body_text),
+				  ast_str_size(*body_text)))) {
+		ast_log(LOG_WARNING, "PIDF body text too large\n");
+		return -1;
+	}
+	*(ast_str_buffer(*body_text) + size) = '\0';
+	ast_str_update(*body_text);
+
+	return 0;
+}
+
+static struct ast_sip_exten_state_provider pidf_xml_provider = {
+	.event_name = "presence",
+	.type = "application",
+	.subtype = "pidf+xml",
+	.body_type = "application/pidf+xml",
+	.create_body = pidf_xml_create_body
+};
+
+static int xpidf_xml_create_body(struct ast_sip_exten_state_data *data, const char *local,
+				 const char *remote, struct ast_str **body_text)
+{
+	static pj_str_t STR_ADDR_PARAM = { ";user=ip", 8 };
+	pjxpidf_pres *pres;
+	pj_xml_attr *attr;
+	pj_str_t name, uri;
+	char *statestring = NULL, *pidfstate = NULL, *pidfnote = NULL;
+	int local_state, size;
+
+	RAII_VAR(pj_pool_t *, pool,
+		 pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(),
+					 "pidf", 1024, 1024), release_pool);
+
+	exten_state_to_str(data->exten_state, &statestring, &pidfstate,
+			   &pidfnote, &local_state);
+
+	if (!(pres = pjxpidf_create(pool, pj_cstr(&name, local)))) {
+		ast_log(LOG_WARNING, "Unable to create PIDF presence\n");
+		return -1;
+	}
+
+	attr = find_node_attr(pool, pres, "atom", "id");
+	pj_strdup2(pool, &attr->value, data->exten);
+
+	attr = find_node_attr(pool, pres, "address", "uri");
+
+	uri.ptr = (char*) pj_pool_alloc(pool, strlen(remote) + STR_ADDR_PARAM.slen);
+	pj_strcpy2( &uri, remote);
+	pj_strcat( &uri, &STR_ADDR_PARAM);
+	pj_strdup(pool, &attr->value, &uri);
+
+	create_attr(pool, pj_xml_find_node(pres, pj_cstr(&name, "address")),
+		    "priority", "0.80000");
+
+	attr = find_node_attr(pool, pres, "status", "status");
+	pj_strdup2(pool, &attr->value,
+		   (local_state ==  NOTIFY_OPEN) ? "open" :
+		   (local_state == NOTIFY_INUSE) ? "inuse" : "closed");
+
+	attr = find_node_attr(pool, pres, "msnsubstatus", "substatus");
+	pj_strdup2(pool, &attr->value,
+		   (local_state == NOTIFY_OPEN) ? "online" :
+		   (local_state == NOTIFY_INUSE) ? "onthephone" : "offline");
+
+	if (!(size = pjxpidf_print(pres, ast_str_buffer(*body_text),
+				  ast_str_size(*body_text)))) {
+		ast_log(LOG_WARNING, "XPIDF body text too large\n");
+		return -1;
+	}
+
+	*(ast_str_buffer(*body_text) + size) = '\0';
+	ast_str_update(*body_text);
+
+	return 0;
+}
+
+static struct ast_sip_exten_state_provider xpidf_xml_provider = {
+	.event_name = "presence",
+	.type = "application",
+	.subtype = "xpidf+xml",
+	.body_type = "application/xpidf+xml",
+	.create_body = xpidf_xml_create_body
+};
+
+static struct ast_sip_exten_state_provider cpim_pidf_xml_provider = {
+	.event_name = "presence",
+	.type = "application",
+	.subtype = "cpim-pidf+xml",
+	.body_type = "application/cpim-pidf+xml",
+	.create_body = xpidf_xml_create_body,
+};
+
+static int load_module(void)
+{
+	if (ast_sip_register_exten_state_provider(&pidf_xml_provider)) {
+		ast_log(LOG_WARNING, "Unable to load provider event_name=%s, body_type=%s",
+			pidf_xml_provider.event_name, pidf_xml_provider.body_type);
+	}
+
+	if (ast_sip_register_exten_state_provider(&xpidf_xml_provider)) {
+		ast_log(LOG_WARNING, "Unable to load provider event_name=%s, body_type=%s",
+			xpidf_xml_provider.event_name, xpidf_xml_provider.body_type);
+	}
+
+	if (ast_sip_register_exten_state_provider(&cpim_pidf_xml_provider)) {
+		ast_log(LOG_WARNING, "Unable to load provider event_name=%s, body_type=%s",
+			cpim_pidf_xml_provider.event_name, cpim_pidf_xml_provider.body_type);
+	}
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	ast_sip_unregister_exten_state_provider(&cpim_pidf_xml_provider);
+	ast_sip_unregister_exten_state_provider(&xpidf_xml_provider);
+	ast_sip_unregister_exten_state_provider(&pidf_xml_provider);
+
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP Extension State PIDF Provider",
+		.load = load_module,
+		.unload = unload_module,
+		.load_pri = AST_MODPRI_CHANNEL_DEPEND,
+);
diff --git a/res/res_sip_pubsub.c b/res/res_sip_pubsub.c
index c8a76a602b..590c96c49a 100644
--- a/res/res_sip_pubsub.c
+++ b/res/res_sip_pubsub.c
@@ -149,16 +149,12 @@ static pjsip_evsub *allocate_evsub(const char *event, enum ast_sip_subscription_
 	if (role == AST_SIP_NOTIFIER) {
 		if (!strcmp(event, "message-summary")) {
 			pjsip_mwi_create_uas(dlg, &pubsub_cb, rdata, &evsub);
-		} else if (!strcmp(event, "presence")) {
-			pjsip_pres_create_uas(dlg, &pubsub_cb, rdata, &evsub);
 		} else {
 			pjsip_evsub_create_uas(dlg, &pubsub_cb, rdata, 0, &evsub);
 		}
 	} else {
 		if (!strcmp(event, "message-summary")) {
 			pjsip_mwi_create_uac(dlg, &pubsub_cb, 0, &evsub);
-		} else if (!strcmp(event, "presence")) {
-			pjsip_pres_create_uac(dlg, &pubsub_cb, 0, &evsub);
 		} else {
 			pj_str_t pj_event;
 			pj_cstr(&pj_event, event);
@@ -239,6 +235,11 @@ pjsip_evsub *ast_sip_subscription_get_evsub(struct ast_sip_subscription *sub)
 	return sub->evsub;
 }
 
+pjsip_dialog *ast_sip_subscription_get_dlg(struct ast_sip_subscription *sub)
+{
+	return sub->dlg;
+}
+
 int ast_sip_subscription_send_request(struct ast_sip_subscription *sub, pjsip_tx_data *tdata)
 {
 	return pjsip_evsub_send_request(ast_sip_subscription_get_evsub(sub),
@@ -340,7 +341,6 @@ static int handler_exists_for_event_name(const char *event_name)
 
 int ast_sip_register_subscription_handler(struct ast_sip_subscription_handler *handler)
 {
-	pj_str_t event;
 	pj_str_t accept[AST_SIP_MAX_ACCEPT];
 	int i;
 
@@ -354,29 +354,29 @@ int ast_sip_register_subscription_handler(struct ast_sip_subscription_handler *h
 		return -1;
 	}
 
-	if (handler_exists_for_event_name(handler->event_name)) {
-		ast_log(LOG_ERROR, "A subscription handler for event %s already exists. Not registering "
-				"new subscription handler\n", handler->event_name);
-		return -1;
-	}
-
-	pj_cstr(&event, handler->event_name);
 	for (i = 0; i < AST_SIP_MAX_ACCEPT && !ast_strlen_zero(handler->accept[i]); ++i) {
 		pj_cstr(&accept[i], handler->accept[i]);
 	}
 
-	if (!strcmp(handler->event_name, "message-summary")) {
-		pjsip_mwi_init_module(ast_sip_get_pjsip_endpoint(), pjsip_evsub_instance());
-	} else if (!strcmp(handler->event_name, "presence")) {
-		pjsip_pres_init_module(ast_sip_get_pjsip_endpoint(), pjsip_evsub_instance());
+	if (!handler_exists_for_event_name(handler->event_name)) {
+		pj_str_t event;
+
+		pj_cstr(&event, handler->event_name);
+
+		if (!strcmp(handler->event_name, "message-summary")) {
+			pjsip_mwi_init_module(ast_sip_get_pjsip_endpoint(), pjsip_evsub_instance());
+		} else {
+			pjsip_evsub_register_pkg(&sub_module, &event, DEFAULT_EXPIRES, i, accept);
+		}
 	} else {
-		pjsip_evsub_register_pkg(&sub_module, &event, DEFAULT_EXPIRES, i, accept);
+		pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), &sub_module, PJSIP_H_ACCEPT, NULL,
+			i, accept);
 	}
 
 	add_handler(handler);
 	return 0;
 }
- 
+
 void ast_sip_unregister_subscription_handler(struct ast_sip_subscription_handler *handler)
 {
 	struct ast_sip_subscription_handler *iter;
@@ -475,7 +475,19 @@ static pj_bool_t sub_on_rx_request(pjsip_rx_data *rdata)
 	}
 	sub = handler->new_subscribe(endpoint, rdata);
 	if (!sub) {
-		pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
+		pjsip_transaction *trans = pjsip_rdata_get_tsx(rdata);
+
+		if (trans) {
+			pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata);
+			pjsip_tx_data *tdata;
+
+			if (pjsip_endpt_create_response(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, &tdata) != PJ_SUCCESS) {
+				return PJ_TRUE;
+			}
+			pjsip_dlg_send_response(dlg, trans, tdata);
+		} else {
+			pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
+		}
 	}
 	return PJ_TRUE;
 }
@@ -515,7 +527,8 @@ static void pubsub_on_tsx_state(pjsip_evsub *evsub, pjsip_transaction *tsx, pjsi
 		return;
 	}
 
-	if (tsx->role == PJSIP_ROLE_UAC && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
+	if (sub->handler->notify_response && tsx->role == PJSIP_ROLE_UAC &&
+	    event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
 		sub->handler->notify_response(sub, event->body.tsx_state.src.rdata);
 	}
 }
@@ -599,7 +612,7 @@ static void pubsub_on_rx_notify(pjsip_evsub *evsub, pjsip_rx_data *rdata, int *p
 		.status_code = 200,
 	};
 
-	if (!sub|| !sub->handler->notify_request) {
+	if (!sub || !sub->handler->notify_request) {
 		return;
 	}
 
diff --git a/res/res_sip_pubsub.exports.in b/res/res_sip_pubsub.exports.in
index 55308746a8..0ef193d8a6 100644
--- a/res/res_sip_pubsub.exports.in
+++ b/res/res_sip_pubsub.exports.in
@@ -4,6 +4,7 @@
 		LINKER_SYMBOL_PREFIXast_sip_subsription_get_endpoint;
 		LINKER_SYMBOL_PREFIXast_sip_subscription_get_serializer;
 		LINKER_SYMBOL_PREFIXast_sip_subscription_get_evsub;
+		LINKER_SYMBOL_PREFIXast_sip_subscription_get_dlg;
 		LINKER_SYMBOL_PREFIXast_sip_subscription_send_request;
 		LINKER_SYMBOL_PREFIXast_sip_subscription_alloc_datastore;
 		LINKER_SYMBOL_PREFIXast_sip_subscription_add_datastore;
diff --git a/res/res_sip_refer.c b/res/res_sip_refer.c
new file mode 100644
index 0000000000..dfc35a3f4b
--- /dev/null
+++ b/res/res_sip_refer.c
@@ -0,0 +1,860 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Joshua Colp <jcolp@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>pjproject</depend>
+	<depend>res_sip</depend>
+	<depend>res_sip_session</depend>
+	<depend>res_sip_pubsub</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_ua.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/res_sip_session.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/taskprocessor.h"
+#include "asterisk/bridging.h"
+#include "asterisk/framehook.h"
+
+/*! \brief REFER Progress structure */
+struct refer_progress {
+	/*! \brief Subscription to provide updates on */
+	pjsip_evsub *sub;
+	/*! \brief Dialog for subscription */
+	pjsip_dialog *dlg;
+	/*! \brief Received packet, used to construct final response in case no subscription exists */
+	pjsip_rx_data *rdata;
+	/*! \brief Frame hook for monitoring REFER progress */
+	int framehook;
+	/*! \brief Last received subclass in frame hook */
+	int subclass;
+	/*! \brief Serializer for notifications */
+	struct ast_taskprocessor *serializer;
+};
+
+/*! \brief REFER Progress notification structure */
+struct refer_progress_notification {
+	/*! \brief Refer progress structure to send notification on */
+	struct refer_progress *progress;
+	/*! \brief SIP response code to send */
+	int response;
+	/*! \brief Subscription state */
+	pjsip_evsub_state state;
+};
+
+/*! \brief REFER Progress module, used to attach REFER progress structure to subscriptions */
+static pjsip_module refer_progress_module = {
+	.name = { "REFER Progress", 14 },
+	.id = -1,
+};
+
+/*! \brief Destructor for REFER Progress notification structure */
+static void refer_progress_notification_destroy(void *obj)
+{
+	struct refer_progress_notification *notification = obj;
+
+	ao2_cleanup(notification->progress);
+}
+
+/*! \brief Allocator for REFER Progress notification structure */
+static struct refer_progress_notification *refer_progress_notification_alloc(struct refer_progress *progress, int response,
+	pjsip_evsub_state state)
+{
+	struct refer_progress_notification *notification = ao2_alloc(sizeof(*notification), refer_progress_notification_destroy);
+
+	if (!notification) {
+		return NULL;
+	}
+
+	ao2_ref(progress, +1);
+	notification->progress = progress;
+	notification->response = response;
+	notification->state = state;
+
+	return notification;
+}
+
+/*! \brief Serialized callback for subscription notification */
+static int refer_progress_notify(void *data)
+{
+	RAII_VAR(struct refer_progress_notification *, notification, data, ao2_cleanup);
+	pjsip_evsub *sub;
+	pjsip_tx_data *tdata;
+
+	/* If the subscription has already been terminated we can't send a notification */
+	if (!(sub = notification->progress->sub)) {
+		ast_debug(3, "Not sending NOTIFY of response '%d' and state '%d' on progress monitor '%p' as subscription has been terminated\n",
+			notification->response, notification->state, notification->progress);
+		return 0;
+	}
+
+	/* If the subscription is being terminated we want to actually remove the progress structure here to
+	 * stop a deadlock from occurring - basically terminated changes the state which queues a synchronous task
+	 * but we are already running a task... thus it would deadlock */
+	if (notification->state == PJSIP_EVSUB_STATE_TERMINATED) {
+		ast_debug(3, "Subscription '%p' is being terminated as a result of a NOTIFY, removing REFER progress structure early on progress monitor '%p'\n",
+			notification->progress->sub, notification->progress);
+		pjsip_dlg_inc_lock(notification->progress->dlg);
+		pjsip_evsub_set_mod_data(notification->progress->sub, refer_progress_module.id, NULL);
+		pjsip_dlg_dec_lock(notification->progress->dlg);
+
+		/* This is for dropping the reference on the subscription */
+		ao2_cleanup(notification->progress);
+
+		notification->progress->sub = NULL;
+	}
+
+	ast_debug(3, "Sending NOTIFY with response '%d' and state '%d' on subscription '%p' and progress monitor '%p'\n",
+		notification->response, notification->state, sub, notification->progress);
+
+	/* Actually send the notification */
+	if (pjsip_xfer_notify(sub, notification->state, notification->response, NULL, &tdata) == PJ_SUCCESS) {
+		pjsip_xfer_send_request(sub, tdata);
+	}
+
+	return 0;
+}
+
+/*! \brief Progress monitoring frame hook - examines frames to determine state of transfer */
+static struct ast_frame *refer_progress_framehook(struct ast_channel *chan, struct ast_frame *f, enum ast_framehook_event event, void *data)
+{
+	struct refer_progress *progress = data;
+	struct refer_progress_notification *notification = NULL;
+
+	/* We only care about frames *to* the channel */
+	if (!f || (event != AST_FRAMEHOOK_EVENT_WRITE)) {
+		return f;
+	}
+
+	/* Determine the state of the REFER based on the control frames (or voice frames) passing */
+	if (f->frametype == AST_FRAME_VOICE && !progress->subclass) {
+		/* Media is passing without progress, this means the call has been answered */
+		notification = refer_progress_notification_alloc(progress, 200, PJSIP_EVSUB_STATE_TERMINATED);
+	} else if (f->frametype == AST_FRAME_CONTROL) {
+		progress->subclass = f->subclass.integer;
+
+		/* Based on the control frame being written we can send a NOTIFY advising of the progress */
+		if ((f->subclass.integer == AST_CONTROL_RING) || (f->subclass.integer == AST_CONTROL_RINGING)) {
+			notification = refer_progress_notification_alloc(progress, 180, PJSIP_EVSUB_STATE_ACTIVE);
+		} else if (f->subclass.integer == AST_CONTROL_BUSY) {
+			notification = refer_progress_notification_alloc(progress, 486, PJSIP_EVSUB_STATE_TERMINATED);
+		} else if (f->subclass.integer == AST_CONTROL_CONGESTION) {
+			notification = refer_progress_notification_alloc(progress, 503, PJSIP_EVSUB_STATE_TERMINATED);
+		} else if (f->subclass.integer == AST_CONTROL_PROGRESS) {
+			notification = refer_progress_notification_alloc(progress, 183, PJSIP_EVSUB_STATE_ACTIVE);
+		} else if (f->subclass.integer == AST_CONTROL_PROCEEDING) {
+			notification = refer_progress_notification_alloc(progress, 100, PJSIP_EVSUB_STATE_ACTIVE);
+		} else if (f->subclass.integer == AST_CONTROL_ANSWER) {
+			notification = refer_progress_notification_alloc(progress, 200, PJSIP_EVSUB_STATE_TERMINATED);
+		}
+	}
+
+	/* If a notification is due to be sent push it to the thread pool */
+	if (notification) {
+		if (ast_sip_push_task(progress->serializer, refer_progress_notify, notification)) {
+			ao2_cleanup(notification);
+		}
+
+		/* If the subscription is being terminated we don't need the frame hook any longer */
+		if (notification->state == PJSIP_EVSUB_STATE_TERMINATED) {
+			ast_debug(3, "Detaching REFER progress monitoring hook from '%s' as subscription is being terminated\n",
+				ast_channel_name(chan));
+			ast_framehook_detach(chan, progress->framehook);
+		}
+
+	}
+
+	return f;
+}
+
+/*! \brief Destroy callback for monitoring framehook */
+static void refer_progress_framehook_destroy(void *data)
+{
+	struct refer_progress *progress = data;
+	struct refer_progress_notification *notification = refer_progress_notification_alloc(progress, 503, PJSIP_EVSUB_STATE_TERMINATED);
+
+	if (notification && ast_sip_push_task(progress->serializer, refer_progress_notify, notification)) {
+		ao2_cleanup(notification);
+	}
+
+	ao2_cleanup(progress);
+}
+
+/*! \brief Serialized callback for subscription termination */
+static int refer_progress_terminate(void *data)
+{
+	struct refer_progress *progress = data;
+
+	/* The subscription is no longer valid */
+	progress->sub = NULL;
+
+	return 0;
+}
+
+/*! \brief Callback for REFER subscription state changes */
+static void refer_progress_on_evsub_state(pjsip_evsub *sub, pjsip_event *event)
+{
+	struct refer_progress *progress = pjsip_evsub_get_mod_data(sub, refer_progress_module.id);
+
+	/* If being destroyed queue it up to the serializer */
+	if (progress && (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED)) {
+		/* To prevent a deadlock race condition we unlock the dialog so other serialized tasks can execute */
+		ast_debug(3, "Subscription '%p' has been remotely terminated, waiting for other tasks to complete on progress monitor '%p'\n",
+			sub, progress);
+
+		/* It's possible that a task is waiting to remove us already, so bump the refcount of progress so it doesn't get destroyed */
+		ao2_ref(progress, +1);
+		pjsip_dlg_dec_lock(progress->dlg);
+		ast_sip_push_task_synchronous(progress->serializer, refer_progress_terminate, progress);
+		pjsip_dlg_inc_lock(progress->dlg);
+		ao2_ref(progress, -1);
+
+		ast_debug(3, "Subscription '%p' removed from progress monitor '%p'\n", sub, progress);
+
+		/* Since it was unlocked it is possible for this to have been removed already, so check again */
+		if (pjsip_evsub_get_mod_data(sub, refer_progress_module.id)) {
+			pjsip_evsub_set_mod_data(sub, refer_progress_module.id, NULL);
+			ao2_cleanup(progress);
+		}
+	}
+}
+
+/*! \brief Callback structure for subscription */
+static pjsip_evsub_user refer_progress_evsub_cb = {
+	.on_evsub_state = refer_progress_on_evsub_state,
+};
+
+/*! \brief Destructor for REFER progress sutrcture */
+static void refer_progress_destroy(void *obj)
+{
+	struct refer_progress *progress = obj;
+
+	ast_taskprocessor_unreference(progress->serializer);
+}
+
+/*! \brief Internal helper function which sets up a refer progress structure if needed */
+static int refer_progress_alloc(struct ast_sip_session *session, pjsip_rx_data *rdata, struct refer_progress **progress)
+{
+	const pj_str_t str_refer_sub = { "Refer-Sub", 9 };
+	pjsip_generic_string_hdr *refer_sub = NULL;
+	const pj_str_t str_true = { "true", 4 };
+	pjsip_tx_data *tdata;
+	pjsip_hdr hdr_list;
+
+	*progress = NULL;
+
+	/* Grab the optional Refer-Sub header, it can be used to suppress the implicit subscription */
+	refer_sub = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_sub, NULL);
+	if ((refer_sub && pj_strnicmp(&refer_sub->hvalue, &str_true, 4))) {
+		return 0;
+	}
+
+	if (!(*progress = ao2_alloc(sizeof(struct refer_progress), refer_progress_destroy))) {
+		return -1;
+	}
+
+	ast_debug(3, "Created progress monitor '%p' for transfer occurring from channel '%s' and endpoint '%s'\n",
+		progress, ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint));
+
+	(*progress)->framehook = -1;
+
+	/* To prevent a potential deadlock we need the dialog so we can lock/unlock */
+	(*progress)->dlg = session->inv_session->dlg;
+
+	if (!((*progress)->serializer = ast_sip_create_serializer())) {
+		goto error;
+	}
+
+	/* Create the implicit subscription for monitoring of this transfer */
+	if (pjsip_xfer_create_uas(session->inv_session->dlg, &refer_progress_evsub_cb, rdata, &(*progress)->sub) != PJ_SUCCESS) {
+		goto error;
+	}
+
+	/* Associate the REFER progress structure with the subscription */
+	ao2_ref(*progress, +1);
+	pjsip_evsub_set_mod_data((*progress)->sub, refer_progress_module.id, *progress);
+
+	pj_list_init(&hdr_list);
+	if (refer_sub) {
+		pjsip_hdr *hdr = (pjsip_hdr*)pjsip_generic_string_hdr_create(session->inv_session->dlg->pool, &str_refer_sub, &str_true);
+
+		pj_list_push_back(&hdr_list, hdr);
+	}
+
+	/* Accept the REFER request */
+	ast_debug(3, "Accepting REFER request for progress monitor '%p'\n", *progress);
+	pjsip_xfer_accept((*progress)->sub, rdata, 202, &hdr_list);
+
+	/* Send initial NOTIFY Request */
+	ast_debug(3, "Sending initial 100 Trying NOTIFY for progress monitor '%p'\n", *progress);
+	if (pjsip_xfer_notify((*progress)->sub, PJSIP_EVSUB_STATE_ACTIVE, 100, NULL, &tdata) == PJ_SUCCESS) {
+		pjsip_xfer_send_request((*progress)->sub, tdata);
+	}
+
+	return 0;
+
+error:
+	ao2_cleanup(*progress);
+	*progress = NULL;
+	return -1;
+}
+
+/*! \brief Structure for attended transfer task */
+struct refer_attended {
+	/*! \brief Transferer session */
+	struct ast_sip_session *transferer;
+	/*! \brief Transferer channel */
+	struct ast_channel *transferer_chan;
+	/*! \brief Second transferer session */
+	struct ast_sip_session *transferer_second	;
+	/*! \brief Optional refer progress structure */
+	struct refer_progress *progress;
+};
+
+/*! \brief Destructor for attended transfer task */
+static void refer_attended_destroy(void *obj)
+{
+	struct refer_attended *attended = obj;
+
+	ao2_cleanup(attended->transferer);
+	ast_channel_unref(attended->transferer_chan);
+	ao2_cleanup(attended->transferer_second);
+}
+
+/*! \brief Allocator for attended transfer task */
+static struct refer_attended *refer_attended_alloc(struct ast_sip_session *transferer, struct ast_sip_session *transferer_second,
+	struct refer_progress *progress)
+{
+	struct refer_attended *attended = ao2_alloc(sizeof(*attended), refer_attended_destroy);
+
+	if (!attended) {
+		return NULL;
+	}
+
+	ao2_ref(transferer, +1);
+	attended->transferer = transferer;
+	ast_channel_ref(transferer->channel);
+	attended->transferer_chan = transferer->channel;
+	ao2_ref(transferer_second, +1);
+	attended->transferer_second = transferer_second;
+
+	if (progress) {
+		ao2_ref(progress, +1);
+		attended->progress = progress;
+	}
+
+	return attended;
+}
+
+/*! \brief Task for attended transfer */
+static int refer_attended(void *data)
+{
+	RAII_VAR(struct refer_attended *, attended, data, ao2_cleanup);
+	int response = 0;
+
+	ast_debug(3, "Performing a REFER attended transfer - Transferer #1: %s Transferer #2: %s\n",
+		ast_channel_name(attended->transferer_chan), ast_channel_name(attended->transferer_second->channel));
+
+	switch (ast_bridge_transfer_attended(attended->transferer_chan, attended->transferer_second->channel)) {
+	case AST_BRIDGE_TRANSFER_INVALID:
+		response = 400;
+		break;
+	case AST_BRIDGE_TRANSFER_NOT_PERMITTED:
+		response = 403;
+		break;
+	case AST_BRIDGE_TRANSFER_FAIL:
+		response = 500;
+		break;
+	case AST_BRIDGE_TRANSFER_SUCCESS:
+		response = 200;
+		ast_sip_session_defer_termination(attended->transferer);
+		break;
+	}
+
+	ast_debug(3, "Final response for REFER attended transfer - Transferer #1: %s Transferer #2: %s is '%d'\n",
+		ast_channel_name(attended->transferer_chan), ast_channel_name(attended->transferer_second->channel), response);
+
+	if (attended->progress && response) {
+		struct refer_progress_notification *notification = refer_progress_notification_alloc(attended->progress, response, PJSIP_EVSUB_STATE_TERMINATED);
+
+		if (notification) {
+			refer_progress_notify(notification);
+		}
+	}
+
+	return 0;
+}
+
+/*! \brief Structure for blind transfer callback details */
+struct refer_blind {
+	/*! \brief Context being used for transfer */
+	const char *context;
+	/*! \brief Optional progress structure */
+	struct refer_progress *progress;
+	/*! \brief REFER message */
+	pjsip_rx_data *rdata;
+	/*! \brief Optional Replaces header */
+	pjsip_replaces_hdr *replaces;
+	/*! \brief Optional Refer-To header */
+	pjsip_sip_uri *refer_to;
+};
+
+/*! \brief Blind transfer callback function */
+static void refer_blind_callback(struct ast_channel *chan, void *user_data, enum ast_transfer_type transfer_type)
+{
+	struct refer_blind *refer = user_data;
+	const pj_str_t str_referred_by = { "Referred-By", 11 };
+	pjsip_generic_string_hdr *referred_by = pjsip_msg_find_hdr_by_name(refer->rdata->msg_info.msg, &str_referred_by, NULL);
+
+	pbx_builtin_setvar_helper(chan, "SIPTRANSFER", "yes");
+
+	/* If progress monitoring is being done attach a frame hook so we can monitor it */
+	if (refer->progress) {
+		struct ast_framehook_interface hook = {
+			.version = AST_FRAMEHOOK_INTERFACE_VERSION,
+			.event_cb = refer_progress_framehook,
+			.destroy_cb = refer_progress_framehook_destroy,
+			.data = refer->progress,
+		};
+
+		/* We need to bump the reference count up on the progress structure since it is in the frame hook now */
+		ao2_ref(refer->progress, +1);
+
+		/* If we can't attach a frame hook for whatever reason send a notification of success immediately */
+		if ((refer->progress->framehook = ast_framehook_attach(chan, &hook)) < 0) {
+			struct refer_progress_notification *notification = refer_progress_notification_alloc(refer->progress, 200,
+				PJSIP_EVSUB_STATE_TERMINATED);
+
+			ast_log(LOG_WARNING, "Could not attach REFER transfer progress monitoring hook to channel '%s' - assuming success\n",
+				ast_channel_name(chan));
+
+			if (notification) {
+				refer_progress_notify(notification);
+			}
+
+			ao2_cleanup(refer->progress);
+		}
+	}
+
+	if (!ast_strlen_zero(refer->context)) {
+		pbx_builtin_setvar_helper(chan, "SIPREFERRINGCONTEXT", refer->context);
+	}
+
+	if (referred_by) {
+		char *uri = referred_by->hvalue.ptr;
+
+		uri[referred_by->hvalue.slen] = '\0';
+		pbx_builtin_setvar_helper(chan, "SIPREFERREDBYHDR", uri);
+	}
+
+	if (refer->replaces) {
+		char replaces[512];
+
+		pjsip_hdr_print_on(refer->replaces, replaces, sizeof(replaces));
+		pbx_builtin_setvar_helper(chan, "SIPREPLACESHDR", replaces);
+	}
+
+	if (refer->refer_to) {
+		char refer_to[PJSIP_MAX_URL_SIZE];
+
+		pjsip_uri_print(PJSIP_URI_IN_REQ_URI, refer->refer_to, refer_to, sizeof(refer_to));
+		pbx_builtin_setvar_helper(chan, "SIPREFERTOHDR", refer_to);
+	}
+}
+
+static int refer_incoming_attended_request(struct ast_sip_session *session, pjsip_rx_data *rdata, pjsip_sip_uri *target_uri,
+	pjsip_param *replaces_param, struct refer_progress *progress)
+{
+	const pj_str_t str_replaces = { "Replaces", 8 };
+	pj_str_t replaces_content;
+	pjsip_replaces_hdr *replaces;
+	int parsed_len;
+	pjsip_dialog *dlg;
+
+	pj_strdup_with_null(rdata->tp_info.pool, &replaces_content, &replaces_param->value);
+
+	/* Parsing the parameter as a Replaces header easily grabs the needed information */
+	if (!(replaces = pjsip_parse_hdr(rdata->tp_info.pool, &str_replaces, replaces_content.ptr,
+		pj_strlen(&replaces_content), &parsed_len))) {
+		ast_log(LOG_ERROR, "Received REFER request on channel '%s' from endpoint '%s' with invalid Replaces header, rejecting\n",
+			ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint));
+		return 400;
+	}
+
+	/* See if the dialog is local, or remote */
+	if ((dlg = pjsip_ua_find_dialog(&replaces->call_id, &replaces->to_tag, &replaces->from_tag, PJ_TRUE))) {
+		RAII_VAR(struct ast_sip_session *, other_session, ast_sip_dialog_get_session(dlg), ao2_cleanup);
+		struct refer_attended *attended;
+
+		pjsip_dlg_dec_lock(dlg);
+
+		if (!other_session) {
+			ast_debug(3, "Received REFER request on channel '%s' from endpoint '%s' for local dialog but no session exists on it\n",
+				ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint));
+			return 603;
+		}
+
+		/* We defer actually doing the attended transfer to the other session so no deadlock can occur */
+		if (!(attended = refer_attended_alloc(session, other_session, progress))) {
+			ast_log(LOG_ERROR, "Received REFER request on channel '%s' from endpoint '%s' for local dialog but could not allocate structure to complete, rejecting\n",
+				ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint));
+			return 500;
+		}
+
+		/* Push it to the other session, which will have both channels with minimal locking */
+		if (ast_sip_push_task(other_session->serializer, refer_attended, attended)) {
+			ao2_cleanup(attended);
+			return 500;
+		}
+
+		ast_debug(3, "Attended transfer from '%s' pushed to second channel serializer\n",
+			ast_channel_name(session->channel));
+
+		return 200;
+	} else {
+		const char *context = (session->channel ? pbx_builtin_getvar_helper(session->channel, "TRANSFER_CONTEXT") : "");
+		struct refer_blind refer = { 0, };
+
+		if (ast_strlen_zero(context)) {
+			context = session->endpoint->context;
+		}
+
+		if (!ast_exists_extension(NULL, context, "external_replaces", 1, NULL)) {
+			ast_log(LOG_ERROR, "Received REFER for remote session on channel '%s' from endpoint '%s' but 'external_replaces' context does not exist for handling\n",
+				ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint));
+			return 404;
+		}
+
+		refer.context = context;
+		refer.progress = progress;
+		refer.rdata = rdata;
+		refer.replaces = replaces;
+		refer.refer_to = target_uri;
+
+		switch (ast_bridge_transfer_blind(session->channel, "external_replaces", context, refer_blind_callback, &refer)) {
+		case AST_BRIDGE_TRANSFER_INVALID:
+			return 400;
+		case AST_BRIDGE_TRANSFER_NOT_PERMITTED:
+			return 403;
+		case AST_BRIDGE_TRANSFER_FAIL:
+			return 500;
+		case AST_BRIDGE_TRANSFER_SUCCESS:
+			ast_sip_session_defer_termination(session);
+			return 200;
+		}
+
+		return 503;
+	}
+
+	return 0;
+}
+
+static int refer_incoming_blind_request(struct ast_sip_session *session, pjsip_rx_data *rdata, pjsip_sip_uri *target,
+	struct refer_progress *progress)
+{
+	const char *context = (session->channel ? pbx_builtin_getvar_helper(session->channel, "TRANSFER_CONTEXT") : "");
+	char exten[AST_MAX_EXTENSION];
+	struct refer_blind refer = { 0, };
+
+	/* If no explicit transfer context has been provided use their configured context */
+	if (ast_strlen_zero(context)) {
+		context = session->endpoint->context;
+	}
+
+	/* Using the user portion of the target URI see if it exists as a valid extension in their context */
+	ast_copy_pj_str(exten, &target->user, sizeof(exten));
+	if (!ast_exists_extension(NULL, context, exten, 1, NULL)) {
+		ast_log(LOG_ERROR, "Channel '%s' from endpoint '%s' attempted blind transfer to '%s@%s' but target does not exist\n",
+			ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint), exten, context);
+		return 404;
+	}
+
+	refer.context = context;
+	refer.progress = progress;
+	refer.rdata = rdata;
+
+	switch (ast_bridge_transfer_blind(session->channel, exten, context, refer_blind_callback, &refer)) {
+	case AST_BRIDGE_TRANSFER_INVALID:
+		return 400;
+	case AST_BRIDGE_TRANSFER_NOT_PERMITTED:
+		return 403;
+	case AST_BRIDGE_TRANSFER_FAIL:
+		return 500;
+	case AST_BRIDGE_TRANSFER_SUCCESS:
+		ast_sip_session_defer_termination(session);
+		return 200;
+	}
+
+	return 503;
+}
+
+/*! \brief Structure used to retrieve channel from another session */
+struct invite_replaces {
+	/*! \brief Session we want the channel from */
+	struct ast_sip_session *session;
+	/*! \brief Channel from the session (with reference) */
+	struct ast_channel *channel;
+	/*! \brief Bridge the channel is in */
+	struct ast_bridge *bridge;
+};
+
+/*! \brief Task for invite replaces */
+static int invite_replaces(void *data)
+{
+	struct invite_replaces *invite = data;
+
+	if (!invite->session->channel) {
+		return -1;
+	}
+
+	ast_channel_ref(invite->session->channel);
+	invite->channel = invite->session->channel;
+
+	ast_channel_lock(invite->channel);
+	invite->bridge = ast_channel_get_bridge(invite->channel);
+	ast_channel_unlock(invite->channel);
+
+	return 0;
+}
+
+static int refer_incoming_invite_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
+{
+	pjsip_dialog *other_dlg = NULL;
+	pjsip_tx_data *packet;
+	int response = 0;
+	RAII_VAR(struct ast_sip_session *, other_session, NULL, ao2_cleanup);
+	struct invite_replaces invite;
+	RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
+
+	/* If a Replaces header is present make sure it is valid */
+	if (pjsip_replaces_verify_request(rdata, &other_dlg, PJ_TRUE, &packet) != PJ_SUCCESS) {
+		response = packet->msg->line.status.code;
+		pjsip_tx_data_dec_ref(packet);
+		goto end;
+	}
+
+	/* If no other dialog exists then this INVITE request does not have a Replaces header */
+	if (!other_dlg) {
+		return 0;
+	}
+
+	other_session = ast_sip_dialog_get_session(other_dlg);
+	pjsip_dlg_dec_lock(other_dlg);
+
+	if (!other_session) {
+		response = 481;
+		ast_debug(3, "INVITE with Replaces received on channel '%s' from endpoint '%s', but requested session does not exist\n",
+			ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint));
+		goto end;
+	}
+
+	invite.session = other_session;
+
+	if (ast_sip_push_task_synchronous(other_session->serializer, invite_replaces, &invite)) {
+		response = 481;
+		goto end;
+	}
+
+	ast_setstate(session->channel, AST_STATE_RING);
+	ast_raw_answer(session->channel);
+
+	if (!invite.bridge) {
+		struct ast_channel *chan = session->channel;
+
+		/* This will use a synchronous task but we aren't operating in the serializer at this point in time, so it
+		 * won't deadlock */
+		if (!ast_channel_move(invite.channel, session->channel)) {
+			ast_hangup(chan);
+		} else {
+			response = 500;
+		}
+	} else {
+		if (ast_bridge_impart(invite.bridge, session->channel, invite.channel, NULL, 1)) {
+			response = 500;
+		}
+	}
+
+	if (!response) {
+		ast_debug(3, "INVITE with Replaces successfully completed on channels '%s' and '%s'\n",
+			ast_channel_name(session->channel), ast_channel_name(invite.channel));
+	}
+
+	ast_channel_unref(invite.channel);
+	ao2_cleanup(invite.bridge);
+
+end:
+	if (response) {
+		ast_debug(3, "INVITE with Replaces failed on channel '%s', sending response of '%d'\n",
+			ast_channel_name(session->channel), response);
+		session->defer_terminate = 1;
+		ast_hangup(session->channel);
+		session->channel = NULL;
+
+		if (pjsip_inv_end_session(session->inv_session, response, NULL, &packet) == PJ_SUCCESS) {
+			ast_sip_session_send_response(session, packet);
+		}
+	}
+
+	return 1;
+}
+
+static int refer_incoming_refer_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
+{
+	const pj_str_t str_refer_to = { "Refer-To", 8 };
+	pjsip_generic_string_hdr *refer_to;
+	char *uri;
+	const pj_str_t str_to = { "To", 2 };
+	pjsip_fromto_hdr *target;
+	pjsip_sip_uri *target_uri;
+	RAII_VAR(struct refer_progress *, progress, NULL, ao2_cleanup);
+	const pj_str_t str_replaces = { "Replaces", 8 };
+	pjsip_param *replaces;
+	int response;
+
+	/* A Refer-To header is required */
+	if (!(refer_to = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_to, NULL))) {
+		pjsip_dlg_respond(session->inv_session->dlg, rdata, 400, NULL, NULL, NULL);
+		ast_debug(3, "Received a REFER without Refer-To on channel '%s' from endpoint '%s'\n",
+			ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint));
+		return 0;
+	}
+	uri = refer_to->hvalue.ptr;
+	uri[refer_to->hvalue.slen] = '\0';
+
+	/* Parse the provided URI string as a To header so we can get the target */
+	if (!(target = pjsip_parse_hdr(rdata->tp_info.pool, &str_to, refer_to->hvalue.ptr, refer_to->hvalue.slen, NULL)) ||
+		(!PJSIP_URI_SCHEME_IS_SIP(target->uri) && !PJSIP_URI_SCHEME_IS_SIPS(target->uri))) {
+		pjsip_dlg_respond(session->inv_session->dlg, rdata, 400, NULL, NULL, NULL);
+		ast_debug(3, "Received a REFER without a parseable Refer-To ('%s') on channel '%s' from endpoint '%s'\n",
+			uri, ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint));
+		return 0;
+	}
+	target_uri = pjsip_uri_get_uri(target->uri);
+
+	/* Set up REFER progress subscription if requested/possible */
+	if (refer_progress_alloc(session, rdata, &progress)) {
+		pjsip_dlg_respond(session->inv_session->dlg, rdata, 500, NULL, NULL, NULL);
+		ast_debug(3, "Could not set up subscription for REFER on channel '%s' from endpoint '%s'\n",
+			ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint));
+		return 0;
+	}
+
+	/* Determine if this is an attended or blind transfer */
+	if ((replaces = pjsip_param_find(&target_uri->header_param, &str_replaces)) ||
+		(replaces = pjsip_param_find(&target_uri->other_param, &str_replaces))) {
+		response = refer_incoming_attended_request(session, rdata, target_uri, replaces, progress);
+	} else {
+		response = refer_incoming_blind_request(session, rdata, target_uri, progress);
+	}
+
+	if (!progress) {
+		/* The transferer has requested no subscription, so send a final response immediately */
+		pjsip_tx_data *tdata;
+		const pj_str_t str_refer_sub = { "Refer-Sub", 9 };
+		const pj_str_t str_false = { "false", 5 };
+		pjsip_hdr *hdr;
+
+		ast_debug(3, "Progress monitoring not requested for REFER on channel '%s' from endpoint '%s', sending immediate response of '%d'\n",
+			ast_channel_name(session->channel), ast_sorcery_object_get_id(session->endpoint), response);
+
+		if (pjsip_dlg_create_response(session->inv_session->dlg, rdata, response, NULL, &tdata) != PJ_SUCCESS) {
+			pjsip_dlg_respond(session->inv_session->dlg, rdata, response, NULL, NULL, NULL);
+			return 0;
+		}
+
+		hdr = (pjsip_hdr*)pjsip_generic_string_hdr_create(tdata->pool, &str_refer_sub, &str_false);
+		pjsip_msg_add_hdr(tdata->msg, hdr);
+
+		pjsip_dlg_send_response(session->inv_session->dlg, pjsip_rdata_get_tsx(rdata), tdata);
+	} else if (response != 200) {
+		/* Since this failed we can send a final NOTIFY now and terminate the subscription */
+		struct refer_progress_notification *notification = refer_progress_notification_alloc(progress, response, PJSIP_EVSUB_STATE_TERMINATED);
+
+		if (notification) {
+			/* The refer_progress_notify function will call ao2_cleanup on this for us */
+			refer_progress_notify(notification);
+		}
+	}
+
+	return 0;
+}
+
+static int refer_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata)
+{
+	if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, pjsip_get_refer_method())) {
+		return refer_incoming_refer_request(session, rdata);
+	} else if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_invite_method)) {
+		return refer_incoming_invite_request(session, rdata);
+	} else {
+		return 0;
+	}
+}
+
+static void refer_outgoing_request(struct ast_sip_session *session, struct pjsip_tx_data *tdata)
+{
+	const char *replaces;
+
+	if (pjsip_method_cmp(&tdata->msg->line.req.method, &pjsip_invite_method) ||
+		!session->channel ||
+		(session->inv_session->state != PJSIP_INV_STATE_CALLING) ||
+		!(replaces = pbx_builtin_getvar_helper(session->channel, "SIPREPLACESHDR"))) {
+		return;
+	}
+
+	ast_sip_add_header(tdata, "Replaces", replaces);
+}
+
+static struct ast_sip_session_supplement refer_supplement = {
+	.priority = AST_SIP_SESSION_SUPPLEMENT_PRIORITY_CHANNEL + 1,
+	.incoming_request = refer_incoming_request,
+	.outgoing_request = refer_outgoing_request,
+};
+
+static int load_module(void)
+{
+	const pj_str_t str_norefersub = { "norefersub", 10 };
+
+	pjsip_replaces_init_module(ast_sip_get_pjsip_endpoint());
+	pjsip_xfer_init_module(ast_sip_get_pjsip_endpoint());
+	pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_SUPPORTED, NULL, 1, &str_norefersub);
+
+	ast_sip_register_service(&refer_progress_module);
+	ast_sip_session_register_supplement(&refer_supplement);
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	ast_sip_session_unregister_supplement(&refer_supplement);
+	ast_sip_unregister_service(&refer_progress_module);
+
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP Blind and Attended Transfer Support",
+		.load = load_module,
+		.unload = unload_module,
+		.load_pri = AST_MODPRI_APP_DEPEND,
+		   );
diff --git a/res/res_sip_registrar.c b/res/res_sip_registrar.c
index c3315d0bec..7661f8d937 100644
--- a/res/res_sip_registrar.c
+++ b/res/res_sip_registrar.c
@@ -90,12 +90,12 @@ static int registrar_validate_contacts(const pjsip_rx_data *rdata, struct ao2_co
 	}
 
 	while ((contact = (pjsip_contact_hdr *) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, contact->next))) {
-		int expiration;
+		int expiration = registrar_get_expiration(aor, contact, rdata);
 		RAII_VAR(struct ast_sip_contact *, existing, NULL, ao2_cleanup);
 
 		if (contact->star) {
 			/* The expiration MUST be 0 when a '*' contact is used and there must be no other contact */
-			if ((contact->expires != 0) || previous) {
+			if ((expiration != 0) || previous) {
 				pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool);
 				return -1;
 			}
@@ -111,7 +111,6 @@ static int registrar_validate_contacts(const pjsip_rx_data *rdata, struct ao2_co
 		}
 
 		details.uri = pjsip_uri_get_uri(contact->uri);
-		expiration = registrar_get_expiration(aor, contact, rdata);
 
 		/* Determine if this is an add, update, or delete for policy enforcement purposes */
 		if (!(existing = ao2_callback(contacts, 0, registrar_find_contact, &details))) {
@@ -199,11 +198,13 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
 	if (ast_strlen_zero(endpoint->aors)) {
 		/* Short circuit early if the endpoint has no AORs configured on it, which means no registration possible */
 		pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
+		ast_sip_report_failed_acl(endpoint, rdata, "registrar_attempt_without_configured_aors");
 		return PJ_TRUE;
 	}
 
 	if (!PJSIP_URI_SCHEME_IS_SIP(rdata->msg_info.to->uri) && !PJSIP_URI_SCHEME_IS_SIPS(rdata->msg_info.to->uri)) {
 		pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 416, NULL, NULL, NULL);
+		ast_sip_report_failed_acl(endpoint, rdata, "registrar_invalid_uri_in_to_received");
 		return PJ_TRUE;
 	}
 
@@ -238,12 +239,14 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
 	if (ast_strlen_zero(aor_name) || !(aor = ast_sip_location_retrieve_aor(aor_name))) {
 		/* The provided AOR name was not found (be it within the configuration or sorcery itself) */
 		pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 404, NULL, NULL, NULL);
+		ast_sip_report_failed_acl(endpoint, rdata, "registrar_requested_aor_not_found");
 		return PJ_TRUE;
 	}
 
 	if (!aor->max_contacts) {
 		/* Registration is not permitted for this AOR */
 		pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
+		ast_sip_report_failed_acl(endpoint, rdata, "registrar_attempt_without_registration_permitted");
 		return PJ_TRUE;
 	}
 
@@ -256,12 +259,14 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
 	if (registrar_validate_contacts(rdata, contacts, aor, &added, &updated, &deleted)) {
 		/* The provided Contact headers do not conform to the specification */
 		pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 400, NULL, NULL, NULL);
+		ast_sip_report_failed_acl(endpoint, rdata, "registrar_invalid_contacts_provided");
 		return PJ_TRUE;
 	}
 
 	if ((MAX(added - deleted, 0) + (!aor->remove_existing ? ao2_container_count(contacts) : 0)) > aor->max_contacts) {
 		/* Enforce the maximum number of contacts */
 		pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
+		ast_sip_report_failed_acl(endpoint, rdata, "registrar_attempt_exceeds_maximum_configured_contacts");
 		return PJ_TRUE;
 	}
 
@@ -304,8 +309,9 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
 				contact_uri, aor_name, expiration);
 		} else if (expiration) {
 			RAII_VAR(struct ast_sip_contact *, updated, ast_sorcery_copy(ast_sip_get_sorcery(), contact), ao2_cleanup);
-
 			updated->expiration_time = ast_tvadd(ast_tvnow(), ast_samp2tv(expiration, 1));
+			updated->qualify_frequency = aor->qualify_frequency;
+			updated->authenticate_qualify = aor->authenticate_qualify;
 
 			ast_sip_location_update_contact(updated);
 			ast_debug(3, "Refreshed contact '%s' on AOR '%s' with new expiration of %d seconds\n",
diff --git a/res/res_sip_registrar_expire.c b/res/res_sip_registrar_expire.c
new file mode 100644
index 0000000000..bbfa7e1181
--- /dev/null
+++ b/res/res_sip_registrar_expire.c
@@ -0,0 +1,227 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Joshua Colp <jcolp@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+	<depend>pjproject</depend>
+	<depend>res_sip</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+
+#include "asterisk/res_sip.h"
+#include "asterisk/module.h"
+#include "asterisk/sched.h"
+
+#define CONTACT_AUTOEXPIRE_BUCKETS 977
+
+static struct ao2_container *contact_autoexpire;
+
+/*! \brief Scheduler used for automatically expiring contacts */
+static struct ast_sched_context *sched;
+
+/*! \brief Structure used for contact auto-expiration */
+struct contact_expiration {
+	/*! \brief Contact that is being auto-expired */
+	struct ast_sip_contact *contact;
+
+	/*! \brief Scheduled item for performing expiration */
+	int sched;
+};
+
+/*! \brief Destructor function for contact auto-expiration */
+static void contact_expiration_destroy(void *obj)
+{
+	struct contact_expiration *expiration = obj;
+
+	ao2_cleanup(expiration->contact);
+}
+
+/*! \brief Hashing function for contact auto-expiration */
+static int contact_expiration_hash(const void *obj, const int flags)
+{
+	const struct contact_expiration *expiration = obj;
+	const char *id = obj;
+
+	return ast_str_hash(flags & OBJ_KEY ? id : ast_sorcery_object_get_id(expiration->contact));
+}
+
+/*! \brief Comparison function for contact auto-expiration */
+static int contact_expiration_cmp(void *obj, void *arg, int flags)
+{
+	struct contact_expiration *expiration1 = obj, *expiration2 = arg;
+	const char *id = arg;
+
+	return !strcmp(ast_sorcery_object_get_id(expiration1->contact), flags & OBJ_KEY ? id :
+		       ast_sorcery_object_get_id(expiration2->contact)) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+/*! \brief Scheduler function which deletes a contact */
+static int contact_expiration_expire(const void *data)
+{
+	RAII_VAR(struct contact_expiration *, expiration, (void*)data, ao2_cleanup);
+
+	expiration->sched = -1;
+
+	/* This will end up invoking the deleted observer callback, which will perform the unlinking and such */
+	ast_sorcery_delete(ast_sip_get_sorcery(), expiration->contact);
+
+	return 0;
+}
+
+/*! \brief Observer callback for when a contact is created */
+static void contact_expiration_observer_created(const void *object)
+{
+	const struct ast_sip_contact *contact = object;
+	RAII_VAR(struct contact_expiration *, expiration, NULL, ao2_cleanup);
+	int expires = MAX(0, ast_tvdiff_ms(contact->expiration_time, ast_tvnow()));
+
+	if (ast_tvzero(contact->expiration_time)) {
+		return;
+	}
+
+	if (!(expiration = ao2_alloc_options(sizeof(*expiration), contact_expiration_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK))) {
+		return;
+	}
+
+	expiration->contact = (struct ast_sip_contact*)contact;
+	ao2_ref(expiration->contact, +1);
+
+	ao2_ref(expiration, +1);
+	if ((expiration->sched = ast_sched_add(sched, expires, contact_expiration_expire, expiration)) < 0) {
+		ao2_cleanup(expiration);
+		ast_log(LOG_ERROR, "Scheduled expiration for contact '%s' could not be performed, contact may persist past life\n",
+			ast_sorcery_object_get_id(contact));
+		return;
+	}
+
+	ao2_link(contact_autoexpire, expiration);
+}
+
+/*! \brief Observer callback for when a contact is updated */
+static void contact_expiration_observer_updated(const void *object)
+{
+	const struct ast_sip_contact *contact = object;
+	RAII_VAR(struct contact_expiration *, expiration, ao2_find(contact_autoexpire, ast_sorcery_object_get_id(contact), OBJ_KEY), ao2_cleanup);
+	int expires = MAX(0, ast_tvdiff_ms(contact->expiration_time, ast_tvnow()));
+
+	if (!expiration) {
+		return;
+	}
+
+	AST_SCHED_REPLACE_UNREF(expiration->sched, sched, expires, contact_expiration_expire, expiration, ao2_cleanup(expiration), ao2_cleanup(expiration), ao2_ref(expiration, +1));
+}
+
+/*! \brief Observer callback for when a contact is deleted */
+static void contact_expiration_observer_deleted(const void *object)
+{
+	RAII_VAR(struct contact_expiration *, expiration, ao2_find(contact_autoexpire, ast_sorcery_object_get_id(object), OBJ_KEY | OBJ_UNLINK), ao2_cleanup);
+
+	if (!expiration) {
+		return;
+	}
+
+	AST_SCHED_DEL_UNREF(sched, expiration->sched, ao2_cleanup(expiration));
+}
+
+/*! \brief Observer callbacks for autoexpiring contacts */
+static struct ast_sorcery_observer contact_expiration_observer = {
+	.created = contact_expiration_observer_created,
+	.updated = contact_expiration_observer_updated,
+	.deleted = contact_expiration_observer_deleted,
+};
+
+/*! \brief Callback function which deletes a contact if it has expired or sets up auto-expiry */
+static int contact_expiration_setup(void *obj, void *arg, int flags)
+{
+	struct ast_sip_contact *contact = obj;
+	int expires = MAX(0, ast_tvdiff_ms(contact->expiration_time, ast_tvnow()));
+
+	if (!expires) {
+		ast_sorcery_delete(ast_sip_get_sorcery(), contact);
+	} else {
+		contact_expiration_observer_created(contact);
+	}
+
+	return 0;
+}
+
+/*! \brief Initialize auto-expiration of any existing contacts */
+static void contact_expiration_initialize_existing(void)
+{
+	RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
+
+	if (!(contacts = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "contact", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL))) {
+		return;
+	}
+
+	ao2_callback(contacts, OBJ_NODATA, contact_expiration_setup, NULL);
+}
+
+static int load_module(void)
+{
+	if (!(contact_autoexpire = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, CONTACT_AUTOEXPIRE_BUCKETS,
+		contact_expiration_hash, contact_expiration_cmp))) {
+		ast_log(LOG_ERROR, "Could not create container for contact auto-expiration\n");
+		return AST_MODULE_LOAD_FAILURE;
+	}
+
+	if (!(sched = ast_sched_context_create())) {
+		ast_log(LOG_ERROR, "Could not create scheduler for contact auto-expiration\n");
+		goto error;
+	}
+
+	if (ast_sched_start_thread(sched)) {
+		ast_log(LOG_ERROR, "Could not start scheduler thread for contact auto-expiration\n");
+		goto error;
+	}
+
+	contact_expiration_initialize_existing();
+
+	if (ast_sorcery_observer_add(ast_sip_get_sorcery(), "contact", &contact_expiration_observer)) {
+		ast_log(LOG_ERROR, "Could not add observer for notifications about contacts for contact auto-expiration\n");
+		goto error;
+	}
+
+	return AST_MODULE_LOAD_SUCCESS;
+
+error:
+	if (sched) {
+		ast_sched_context_destroy(sched);
+	}
+
+	ao2_cleanup(contact_autoexpire);
+	return AST_MODULE_LOAD_FAILURE;
+}
+
+static int unload_module(void)
+{
+	ast_sorcery_observer_remove(ast_sip_get_sorcery(), "contact", &contact_expiration_observer);
+	ast_sched_context_destroy(sched);
+	ao2_cleanup(contact_autoexpire);
+
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP Contact Auto-Expiration",
+		.load = load_module,
+		.unload = unload_module,
+		.load_pri = AST_MODPRI_APP_DEPEND,
+	       );
diff --git a/res/res_sip_sdp_rtp.c b/res/res_sip_sdp_rtp.c
index b0c8ae31ca..bc150ed4a2 100644
--- a/res/res_sip_sdp_rtp.c
+++ b/res/res_sip_sdp_rtp.c
@@ -47,6 +47,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/causes.h"
 #include "asterisk/sched.h"
 #include "asterisk/acl.h"
+#include "asterisk/sdp_srtp.h"
 
 #include "asterisk/res_sip.h"
 #include "asterisk/res_sip_session.h"
@@ -117,6 +118,10 @@ static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_me
 	ast_rtp_codecs_packetization_set(ast_rtp_instance_get_codecs(session_media->rtp),
 					 session_media->rtp, &session->endpoint->prefs);
 
+	if (session->endpoint->dtmf == AST_SIP_DTMF_INBAND) {
+		ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_INBAND);
+	}
+
 	if (!session->endpoint->ice_support && (ice = ast_rtp_instance_get_ice(session_media->rtp))) {
 		ice->stop(session_media->rtp);
 	}
@@ -289,6 +294,18 @@ static pjmedia_sdp_attr* generate_fmtp_attr(pj_pool_t *pool, struct ast_format *
 	return attr;
 }
 
+static int codec_pref_has_type(struct ast_codec_pref *prefs, enum ast_format_type media_type)
+{
+	int i;
+	struct ast_format fmt;
+	for (i = 0; ast_codec_pref_index(prefs, i, &fmt); ++i) {
+		if (AST_FORMAT_GET_TYPE(fmt.id) == media_type) {
+			return 1;
+		}
+	}
+	return 0;
+}
+
 /*! \brief Function which adds ICE attributes to a media stream */
 static void add_ice_to_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, pj_pool_t *pool, pjmedia_sdp_media *media)
 {
@@ -460,6 +477,101 @@ static void apply_packetization(struct ast_sip_session *session, struct ast_sip_
 					 session_media->rtp, pref);
 }
 
+/*! \brief figure out media transport encryption type from the media transport string */
+static enum ast_sip_session_media_encryption get_media_encryption_type(pj_str_t transport)
+{
+	RAII_VAR(char *, transport_str, ast_strndup(transport.ptr, transport.slen), ast_free);
+	if (strstr(transport_str, "UDP/TLS")) {
+		return AST_SIP_MEDIA_ENCRYPT_DTLS;
+	} else if (strstr(transport_str, "SAVP")) {
+		return AST_SIP_MEDIA_ENCRYPT_SDES;
+	} else {
+		return AST_SIP_MEDIA_ENCRYPT_NONE;
+	}
+}
+
+/*!
+ * \brief Checks whether the encryption offered in SDP is compatible with the endpoint's configuration
+ * \internal
+ *
+ * \param endpoint_encryption Media encryption configured for the endpoint
+ * \param stream pjmedia_sdp_media stream description
+ *
+ * \retval AST_SIP_MEDIA_TRANSPORT_INVALID on encryption mismatch
+ * \retval The encryption requested in the SDP
+ */
+static enum ast_sip_session_media_encryption check_endpoint_media_transport(
+	struct ast_sip_endpoint *endpoint,
+	const struct pjmedia_sdp_media *stream)
+{
+	enum ast_sip_session_media_encryption incoming_encryption;
+
+	if (endpoint->use_avpf) {
+		char transport_end = stream->desc.transport.ptr[stream->desc.transport.slen - 1];
+		if (transport_end != 'F') {
+			return AST_SIP_MEDIA_TRANSPORT_INVALID;
+		}
+	}
+
+	incoming_encryption = get_media_encryption_type(stream->desc.transport);
+	if (incoming_encryption == AST_SIP_MEDIA_ENCRYPT_DTLS) {
+		/* DTLS not yet supported */
+		return AST_SIP_MEDIA_TRANSPORT_INVALID;
+	}
+
+	if (incoming_encryption == endpoint->media_encryption) {
+		return incoming_encryption;
+	}
+
+	return AST_SIP_MEDIA_TRANSPORT_INVALID;
+}
+
+static int setup_sdes_srtp(struct ast_sip_session_media *session_media,
+	const struct pjmedia_sdp_media *stream)
+{
+	int i;
+
+	for (i = 0; i < stream->attr_count; i++) {
+		pjmedia_sdp_attr *attr;
+		RAII_VAR(char *, crypto_str, NULL, ast_free);
+
+		/* check the stream for the required crypto attribute */
+		attr = stream->attr[i];
+		if (pj_strcmp2(&attr->name, "crypto")) {
+			continue;
+		}
+
+		crypto_str = ast_strndup(attr->value.ptr, attr->value.slen);
+		if (!crypto_str) {
+			return -1;
+		}
+
+		if (!session_media->srtp) {
+			session_media->srtp = ast_sdp_srtp_alloc();
+			if (!session_media->srtp) {
+				return -1;
+			}
+		}
+
+		if (!session_media->srtp->crypto) {
+			session_media->srtp->crypto = ast_sdp_crypto_alloc();
+			if (!session_media->srtp->crypto) {
+				return -1;
+			}
+		}
+
+		if (!ast_sdp_crypto_process(session_media->rtp, session_media->srtp, crypto_str)) {
+			/* found a valid crypto attribute */
+			return 0;
+		}
+
+		ast_debug(1, "Ignoring crypto offer with unsupported parameters: %s\n", crypto_str);
+	}
+
+	/* no usable crypto attributes found */
+	return -1;
+}
+
 /*! \brief Function which negotiates an incoming media stream */
 static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
 					 const struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream)
@@ -467,12 +579,19 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct
 	char host[NI_MAXHOST];
 	RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free_ptr);
 	enum ast_format_type media_type = stream_to_media_type(session_media->stream_type);
+	enum ast_sip_session_media_encryption incoming_encryption;
 
 	/* If no type formats have been configured reject this stream */
 	if (!ast_format_cap_has_type(session->endpoint->codecs, media_type)) {
 		return 0;
 	}
 
+	/* Ensure incoming transport is compatible with the endpoint's configuration */
+	incoming_encryption = check_endpoint_media_transport(session->endpoint, stream);
+	if (incoming_encryption == AST_SIP_MEDIA_TRANSPORT_INVALID) {
+		return -1;
+	}
+
 	ast_copy_pj_str(host, stream->conn ? &stream->conn->addr : &sdp->conn->addr, sizeof(host));
 
 	/* Ensure that the address provided is valid */
@@ -486,9 +605,42 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct
 		return -1;
 	}
 
+	if (incoming_encryption == AST_SIP_MEDIA_ENCRYPT_SDES
+			&& setup_sdes_srtp(session_media, stream)) {
+		return -1;
+	}
+
 	return set_caps(session, session_media, stream);
 }
 
+static int add_crypto_to_stream(struct ast_sip_session *session,
+	struct ast_sip_session_media *session_media,
+	pj_pool_t *pool, pjmedia_sdp_media *media)
+{
+	pj_str_t stmp;
+	pjmedia_sdp_attr *attr;
+	const char *crypto_attribute;
+
+	if (!session_media->srtp && session->endpoint->media_encryption != AST_SIP_MEDIA_ENCRYPT_NONE) {
+		session_media->srtp = ast_sdp_srtp_alloc();
+		if (!session_media->srtp) {
+			return -1;
+		}
+	}
+
+	crypto_attribute = ast_sdp_srtp_get_attrib(session_media->srtp,
+		0 /* DTLS can not be enabled for res_sip */,
+		0 /* don't prefer 32byte tag length */);
+	if (!crypto_attribute) {
+		/* No crypto attribute to add */
+		return -1;
+	}
+
+	attr = pjmedia_sdp_attr_create(pool, "crypto", pj_cstr(&stmp, crypto_attribute));
+	media->attr[media->attr_count++] = attr;
+	return 0;
+}
+
 /*! \brief Function which creates an outgoing stream */
 static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
 				      struct pjmedia_sdp_session *sdp)
@@ -497,7 +649,6 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
 	static const pj_str_t STR_IN = { "IN", 2 };
 	static const pj_str_t STR_IP4 = { "IP4", 3};
 	static const pj_str_t STR_IP6 = { "IP6", 3};
-	static const pj_str_t STR_RTP_AVP = { "RTP/AVP", 7 };
 	static const pj_str_t STR_SENDRECV = { "sendrecv", 8 };
 	pjmedia_sdp_media *media;
 	char hostip[PJ_INET6_ADDRSTRLEN+2];
@@ -508,14 +659,19 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
 	int index = 0, min_packet_size = 0, noncodec = (session->endpoint->dtmf == AST_SIP_DTMF_RFC_4733) ? AST_RTP_DTMF : 0;
 	int rtp_code;
 	struct ast_format format;
-	struct ast_format compat_format;
 	RAII_VAR(struct ast_format_cap *, caps, NULL, ast_format_cap_destroy);
 	enum ast_format_type media_type = stream_to_media_type(session_media->stream_type);
+	int crypto_res;
 
 	int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) &&
 		!ast_format_cap_is_empty(session->direct_media_cap);
 
-	if (!ast_format_cap_has_type(session->endpoint->codecs, media_type)) {
+	int use_override_prefs = session->override_prefs.formats[0].id;
+	struct ast_codec_pref *prefs = use_override_prefs ?
+		&session->override_prefs : &session->endpoint->prefs;
+
+	if ((use_override_prefs && !codec_pref_has_type(&session->override_prefs, media_type)) ||
+	    (!use_override_prefs && !ast_format_cap_has_type(session->endpoint->codecs, media_type))) {
 		/* If no type formats are configured don't add a stream */
 		return 0;
 	} else if (!session_media->rtp && create_rtp(session, session_media, session->endpoint->rtp_ipv6)) {
@@ -527,9 +683,11 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
 		return -1;
 	}
 
-	/* TODO: This should eventually support SRTP */
+	crypto_res = add_crypto_to_stream(session, session_media, pool, media);
+
 	media->desc.media = pj_str(session_media->stream_type);
-	media->desc.transport = STR_RTP_AVP;
+	media->desc.transport = pj_str(ast_sdp_get_rtp_profile(
+		!crypto_res, session_media->rtp, session->endpoint->use_avpf));
 
 	/* Add connection level details */
 	if (direct_media_enabled) {
@@ -565,36 +723,36 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
 	} else if (ast_format_cap_is_empty(session->req_caps) || !ast_format_cap_has_joint(session->req_caps, session->endpoint->codecs)) {
 		ast_format_cap_copy(caps, session->endpoint->codecs);
 	} else {
-		ast_format_cap_joint_copy(session->endpoint->codecs, session->req_caps, caps);
+		ast_format_cap_copy(caps, session->req_caps);
 	}
 
-	for (index = 0; ast_codec_pref_index(&session->endpoint->prefs, index, &format); ++index) {
+	for (index = 0; ast_codec_pref_index(prefs, index, &format); ++index) {
 		struct ast_codec_pref *pref = &ast_rtp_instance_get_codecs(session_media->rtp)->pref;
 
 		if (AST_FORMAT_GET_TYPE(format.id) != media_type) {
 			continue;
 		}
 
-		if (!ast_format_cap_get_compatible_format(caps, &format, &compat_format)) {
+		if (!use_override_prefs && !ast_format_cap_get_compatible_format(caps, &format, &format)) {
 			continue;
 		}
 
-		if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media->rtp), 1, &compat_format, 0)) == -1) {
+		if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media->rtp), 1, &format, 0)) == -1) {
 			return -1;
 		}
 
-		if (!(attr = generate_rtpmap_attr(media, pool, rtp_code, 1, &compat_format, 0))) {
+		if (!(attr = generate_rtpmap_attr(media, pool, rtp_code, 1, &format, 0))) {
 			continue;
 		}
 
 		media->attr[media->attr_count++] = attr;
 
-		if ((attr = generate_fmtp_attr(pool, &compat_format, rtp_code))) {
+		if ((attr = generate_fmtp_attr(pool, &format, rtp_code))) {
 			media->attr[media->attr_count++] = attr;
 		}
 
 		if (pref && media_type != AST_FORMAT_TYPE_VIDEO) {
-			struct ast_format_list fmt = ast_codec_pref_getsize(pref, &compat_format);
+			struct ast_format_list fmt = ast_codec_pref_getsize(pref, &format);
 			if (fmt.cur_ms && ((fmt.cur_ms < min_packet_size) || !min_packet_size)) {
 				min_packet_size = fmt.cur_ms;
 			}
@@ -768,9 +926,9 @@ static int video_info_incoming_request(struct ast_sip_session *session, struct p
 	struct pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata);
 	pjsip_tx_data *tdata;
 
-	if (pj_strcmp2(&rdata->msg_info.msg->body->content_type.type, "application") ||
-	    pj_strcmp2(&rdata->msg_info.msg->body->content_type.subtype, "media_control+xml")) {
-
+	if (!ast_sip_is_content_type(&rdata->msg_info.msg->body->content_type,
+				     "application",
+				     "media_control+xml")) {
 		return 0;
 	}
 
diff --git a/res/res_sip_session.c b/res/res_sip_session.c
index 7be75ab1d8..9668b73e9e 100644
--- a/res/res_sip_session.c
+++ b/res/res_sip_session.c
@@ -40,6 +40,8 @@
 #include "asterisk/pbx.h"
 #include "asterisk/taskprocessor.h"
 #include "asterisk/causes.h"
+#include "asterisk/sdp_srtp.h"
+#include "asterisk/dsp.h"
 
 #define SDP_HANDLER_BUCKETS 11
 
@@ -335,10 +337,33 @@ static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sd
 		char media[20];
 		struct ast_sip_session_sdp_handler *handler;
 		RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
+		RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);
 
 		/* We need a null-terminated version of the media string */
 		ast_copy_pj_str(media, &sdp->media[i]->desc.media, sizeof(media));
 
+		session_media = ao2_find(session->media, media, OBJ_KEY);
+		if (!session_media) {
+			/* if the session_media doesn't exist, there weren't
+			 * any handlers at the time of its creation */
+			continue;
+		}
+
+		if (session_media->handler) {
+			int res;
+			handler = session_media->handler;
+			res = handler->negotiate_incoming_sdp_stream(
+				session, session_media, sdp, sdp->media[i]);
+			if (res <= 0) {
+				/* Catastrophic failure or ignored by assigned handler. Abort! */
+				return -1;
+			}
+			if (res > 0) {
+				/* Handled by this handler. Move to the next stream */
+				continue;
+			}
+		}
+
 		handler_list = ao2_find(sdp_handlers, media, OBJ_KEY);
 		if (!handler_list) {
 			ast_debug(1, "No registered SDP handlers for media type '%s'\n", media);
@@ -346,9 +371,7 @@ static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sd
 		}
 		AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
 			int res;
-			RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);
-			session_media = ao2_find(session->media, handler_list->stream_type, OBJ_KEY);
-			if (!session_media || session_media->handler) {
+			if (session_media->handler) {
 				/* There is only one slot for this stream type and it has already been claimed
 				 * so it will go unhandled */
 				break;
@@ -710,7 +733,7 @@ int ast_sip_session_refresh(struct ast_sip_session *session,
 		return 0;
 	}
 
-	if (inv_session->invite_tsx) {
+	if ((method == AST_SIP_SESSION_REFRESH_METHOD_INVITE) && inv_session->invite_tsx) {
 		/* We can't send a reinvite yet, so delay it */
 		ast_debug(3, "Delaying sending reinvite to %s because of outstanding transaction...\n",
 				ast_sorcery_object_get_id(session->endpoint));
@@ -787,6 +810,23 @@ void ast_sip_session_send_request(struct ast_sip_session *session, pjsip_tx_data
 	ast_sip_session_send_request_with_cb(session, tdata, NULL);
 }
 
+int ast_sip_session_create_invite(struct ast_sip_session *session, pjsip_tx_data **tdata)
+{
+	pjmedia_sdp_session *offer;
+
+	if (!(offer = create_local_sdp(session->inv_session, session, NULL))) {
+		pjsip_inv_terminate(session->inv_session, 500, PJ_FALSE);
+		return -1;
+	}
+
+	pjsip_inv_set_local_sdp(session->inv_session, offer);
+	pjmedia_sdp_neg_set_prefer_remote_codec_order(session->inv_session->neg, PJ_FALSE);
+	if (pjsip_inv_invite(session->inv_session, tdata) != PJ_SUCCESS) {
+		return -1;
+	}
+	return 0;
+}
+
 /*!
  * \brief Called when the PJSIP core loads us
  *
@@ -859,6 +899,9 @@ static void session_media_dtor(void *obj)
 	if (session_media->handler) {
 		session_media->handler->stream_destroy(session_media);
 	}
+	if (session_media->srtp) {
+		ast_sdp_srtp_destroy(session_media->srtp);
+	}
 }
 
 static void session_destructor(void *obj)
@@ -888,6 +931,10 @@ static void session_destructor(void *obj)
 	ao2_cleanup(session->endpoint);
 	ast_format_cap_destroy(session->req_caps);
 
+	if (session->dsp) {
+		ast_dsp_free(session->dsp);
+	}
+
 	if (session->inv_session) {
 		pjsip_dlg_dec_session(session->inv_session->dlg, &session_module);
 	}
@@ -956,6 +1003,14 @@ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint,
 	session->inv_session = inv_session;
 	session->req_caps = ast_format_cap_alloc_nolock();
 
+	if (endpoint->dtmf == AST_SIP_DTMF_INBAND) {
+		if (!(session->dsp = ast_dsp_new())) {
+			return NULL;
+		}
+
+		ast_dsp_set_features(session->dsp, DSP_FEATURE_DIGIT_DETECT);
+	}
+
 	if (add_supplements(session)) {
 		return NULL;
 	}
@@ -991,7 +1046,6 @@ struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint
 	pjsip_dialog *dlg;
 	struct pjsip_inv_session *inv_session;
 	RAII_VAR(struct ast_sip_session *, session, NULL, ao2_cleanup);
-	pjmedia_sdp_session *offer;
 
 	/* If no location has been provided use the AOR list from the endpoint itself */
 	location = S_OR(location, endpoint->aors);
@@ -1033,16 +1087,68 @@ struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint
 	}
 
 	ast_format_cap_copy(session->req_caps, req_caps);
-	if ((pjsip_dlg_add_usage(dlg, &session_module, NULL) != PJ_SUCCESS) ||
-	    !(offer = create_local_sdp(inv_session, session, NULL))) {
+	if ((pjsip_dlg_add_usage(dlg, &session_module, NULL) != PJ_SUCCESS)) {
 		pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
 		return NULL;
 	}
 
-	pjsip_inv_set_local_sdp(inv_session, offer);
-	pjmedia_sdp_neg_set_prefer_remote_codec_order(inv_session->neg, PJ_FALSE);
+	ao2_ref(session, +1);
+	return session;
+}
+
+static int session_termination_task(void *data)
+{
+	RAII_VAR(struct ast_sip_session *, session, data, ao2_cleanup);
+	pjsip_tx_data *packet = NULL;
+
+	if (!session->inv_session) {
+		return 0;
+	}
+
+	if (pjsip_inv_end_session(session->inv_session, 603, NULL, &packet) == PJ_SUCCESS) {
+		ast_sip_session_send_request(session, packet);
+	}
+
+	return 0;
+}
+
+static void session_termination_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry)
+{
+	struct ast_sip_session *session = entry->user_data;
+
+	if (ast_sip_push_task(session->serializer, session_termination_task, session)) {
+		ao2_cleanup(session);
+	}
+}
+
+void ast_sip_session_defer_termination(struct ast_sip_session *session)
+{
+	pj_time_val delay = { .sec = 60, };
+
+	session->defer_terminate = 1;
+
+	session->scheduled_termination.id = 0;
+	ao2_ref(session, +1);
+	session->scheduled_termination.user_data = session;
+	session->scheduled_termination.cb = session_termination_cb;
+
+	if (pjsip_endpt_schedule_timer(ast_sip_get_pjsip_endpoint(), &session->scheduled_termination, &delay) != PJ_SUCCESS) {
+		ao2_ref(session, -1);
+	}
+}
+
+struct ast_sip_session *ast_sip_dialog_get_session(pjsip_dialog *dlg)
+{
+	pjsip_inv_session *inv_session = pjsip_dlg_get_inv_session(dlg);
+	struct ast_sip_session *session;
+
+	if (!inv_session ||
+		!(session = inv_session->mod_data[session_module.id])) {
+		return NULL;
+	}
 
 	ao2_ref(session, +1);
+
 	return session;
 }
 
@@ -1102,11 +1208,11 @@ static pjsip_inv_session *pre_session_setup(pjsip_rx_data *rdata, const struct a
 		return NULL;
 	}
 	if (pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, NULL, &dlg) != PJ_SUCCESS) {
-		pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL);
-        return NULL;
+		pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
+		return NULL;
 	}
 	if (pjsip_inv_create_uas(dlg, rdata, NULL, 0, &inv_session) != PJ_SUCCESS) {
-		pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL);
+		pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
 		pjsip_dlg_terminate(dlg);
 		return NULL;
 	}
@@ -1223,7 +1329,20 @@ static void handle_new_invite_request(pjsip_rx_data *rdata)
 	handle_incoming_request(session, rdata);
 }
 
-static int has_supplement(struct ast_sip_session *session, pjsip_rx_data *rdata)
+static pj_bool_t does_method_match(const pj_str_t *message_method, const char *supplement_method)
+{
+	pj_str_t method;
+
+	if (ast_strlen_zero(supplement_method)) {
+		return PJ_TRUE;
+	}
+
+	pj_cstr(&method, supplement_method);
+
+	return pj_stristr(&method, message_method) ? PJ_TRUE : PJ_FALSE;
+}
+
+static pj_bool_t has_supplement(const struct ast_sip_session *session, const pjsip_rx_data *rdata)
 {
 	struct ast_sip_session_supplement *supplement;
 	struct pjsip_method *method = &rdata->msg_info.msg->line.req.method;
@@ -1233,7 +1352,7 @@ static int has_supplement(struct ast_sip_session *session, pjsip_rx_data *rdata)
 	}
 
 	AST_LIST_TRAVERSE(&session->supplements, supplement, next) {
-		if (!supplement->method || !pj_strcmp2(&method->name, supplement->method)) {
+		if (does_method_match(&method->name, supplement->method)) {
 			return PJ_TRUE;
 		}
 	}
@@ -1383,9 +1502,10 @@ static void handle_incoming_request(struct ast_sip_session *session, pjsip_rx_da
 
 	ast_debug(3, "Method is %.*s\n", (int) pj_strlen(&req.method.name), pj_strbuf(&req.method.name));
 	AST_LIST_TRAVERSE(&session->supplements, supplement, next) {
-		if (supplement->incoming_request && (
-				!supplement->method || !pj_strcmp2(&req.method.name, supplement->method))) {
-			supplement->incoming_request(session, rdata);
+		if (supplement->incoming_request && does_method_match(&req.method.name, supplement->method)) {
+			if (supplement->incoming_request(session, rdata)) {
+				break;
+			}
 		}
 	}
 }
@@ -1399,8 +1519,7 @@ static void handle_incoming_response(struct ast_sip_session *session, pjsip_rx_d
 			pj_strbuf(&status.reason));
 
 	AST_LIST_TRAVERSE(&session->supplements, supplement, next) {
-		if (supplement->incoming_response && (
-				!supplement->method || !pj_strcmp2(&rdata->msg_info.cseq->method.name, supplement->method))) {
+		if (supplement->incoming_response && does_method_match(&rdata->msg_info.cseq->method.name, supplement->method)) {
 			supplement->incoming_response(session, rdata);
 		}
 	}
@@ -1427,8 +1546,7 @@ static void handle_outgoing_request(struct ast_sip_session *session, pjsip_tx_da
 
 	ast_debug(3, "Method is %.*s\n", (int) pj_strlen(&req.method.name), pj_strbuf(&req.method.name));
 	AST_LIST_TRAVERSE(&session->supplements, supplement, next) {
-		if (supplement->outgoing_request &&
-				(!supplement->method || !pj_strcmp2(&req.method.name, supplement->method))) {
+		if (supplement->outgoing_request && does_method_match(&req.method.name, supplement->method)) {
 			supplement->outgoing_request(session, tdata);
 		}
 	}
@@ -1438,15 +1556,13 @@ static void handle_outgoing_response(struct ast_sip_session *session, pjsip_tx_d
 {
 	struct ast_sip_session_supplement *supplement;
 	struct pjsip_status_line status = tdata->msg->line.status;
-	ast_debug(3, "Response is %d %.*s\n", status.code, (int) pj_strlen(&status.reason),
-			pj_strbuf(&status.reason));
+	pjsip_cseq_hdr *cseq = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
+	ast_debug(3, "Method is %.*s, Response is %d %.*s\n", (int) pj_strlen(&cseq->method.name),
+		pj_strbuf(&cseq->method.name), status.code, (int) pj_strlen(&status.reason),
+		pj_strbuf(&status.reason));
 
 	AST_LIST_TRAVERSE(&session->supplements, supplement, next) {
-		/* XXX Not sure how to get the method from a response.
-		 * For now, just call supplements on all responses, no
-		 * matter the method. This is less than ideal
-		 */
-		if (supplement->outgoing_response) {
+		if (supplement->outgoing_response && does_method_match(&cseq->method.name, supplement->method)) {
 			supplement->outgoing_response(session, tdata);
 		}
 	}
@@ -1467,6 +1583,11 @@ static int session_end(struct ast_sip_session *session)
 {
 	struct ast_sip_session_supplement *iter;
 
+	/* Stop the scheduled termination */
+	if (pj_timer_heap_cancel(pjsip_endpt_get_timer_heap(ast_sip_get_pjsip_endpoint()), &session->scheduled_termination)) {
+		ao2_ref(session, -1);
+	}
+
 	/* Session is dead. Let's get rid of the reference to the session */
 	AST_LIST_TRAVERSE(&session->supplements, iter, next) {
 		if (iter->session_end) {
@@ -1714,8 +1835,17 @@ static void session_inv_on_media_update(pjsip_inv_session *inv, pj_status_t stat
 
 static pjsip_redirect_op session_inv_on_redirected(pjsip_inv_session *inv, const pjsip_uri *target, const pjsip_event *e)
 {
-	/* XXX STUB */
-	return PJSIP_REDIRECT_REJECT;
+	struct ast_sip_session *session = inv->mod_data[session_module.id];
+
+	if (PJSIP_URI_SCHEME_IS_SIP(target) || PJSIP_URI_SCHEME_IS_SIPS(target)) {
+		const pjsip_sip_uri *uri = pjsip_uri_get_uri(target);
+		char exten[AST_MAX_EXTENSION];
+
+		ast_copy_pj_str(exten, &uri->user, sizeof(exten));
+		ast_channel_call_forward_set(session->channel, exten);
+	}
+
+	return PJSIP_REDIRECT_STOP;
 }
 
 static pjsip_inv_callback inv_callback = {
diff --git a/res/res_sip_session.exports.in b/res/res_sip_session.exports.in
index 08c6f39379..28ed0b2399 100644
--- a/res/res_sip_session.exports.in
+++ b/res/res_sip_session.exports.in
@@ -1,5 +1,6 @@
 {
 	global:
+		LINKER_SYMBOL_PREFIXast_sip_session_defer_termination;
 		LINKER_SYMBOL_PREFIXast_sip_session_register_sdp_handler;
 		LINKER_SYMBOL_PREFIXast_sip_session_unregister_sdp_handler;
 		LINKER_SYMBOL_PREFIXast_sip_session_register_supplement;
@@ -12,7 +13,9 @@
 		LINKER_SYMBOL_PREFIXast_sip_session_refresh;
 		LINKER_SYMBOL_PREFIXast_sip_session_send_response;
 		LINKER_SYMBOL_PREFIXast_sip_session_send_request;
+		LINKER_SYMBOL_PREFIXast_sip_session_create_invite;
 		LINKER_SYMBOL_PREFIXast_sip_session_create_outgoing;
+		LINKER_SYMBOL_PREFIXast_sip_dialog_get_session;
 	local:
 		*;
 };
diff --git a/res/res_sip_transport_websocket.c b/res/res_sip_transport_websocket.c
new file mode 100644
index 0000000000..e83011cfcc
--- /dev/null
+++ b/res/res_sip_transport_websocket.c
@@ -0,0 +1,402 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Jason Parker <jparker@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \brief WebSocket transport module
+ */
+
+/*** MODULEINFO
+	<depend>pjproject</depend>
+	<depend>res_sip</depend>
+	<depend>res_http_websocket</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include <pjsip.h>
+#include <pjsip_ua.h>
+
+#include "asterisk/module.h"
+#include "asterisk/http_websocket.h"
+#include "asterisk/res_sip.h"
+#include "asterisk/res_sip_session.h"
+#include "asterisk/taskprocessor.h"
+
+static int transport_type_ws;
+static int transport_type_wss;
+
+/*!
+ * \brief Wrapper for pjsip_transport, for storing the WebSocket session
+ */
+struct ws_transport {
+	pjsip_transport transport;
+	pjsip_rx_data rdata;
+	struct ast_websocket *ws_session;
+};
+
+/*!
+ * \brief Send a message over the WebSocket connection.
+ *
+ * Called by pjsip transport manager.
+ */
+static pj_status_t ws_send_msg(pjsip_transport *transport,
+                            pjsip_tx_data *tdata,
+                            const pj_sockaddr_t *rem_addr,
+                            int addr_len,
+                            void *token,
+                            pjsip_transport_callback callback)
+{
+	struct ws_transport *wstransport = (struct ws_transport *)transport;
+
+	if (ast_websocket_write(wstransport->ws_session, AST_WEBSOCKET_OPCODE_TEXT, tdata->buf.start, (int)(tdata->buf.cur - tdata->buf.start))) {
+		return PJ_EUNKNOWN;
+	}
+
+	return PJ_SUCCESS;
+}
+
+/*!
+ * \brief Destroy the pjsip transport.
+ *
+ * Called by pjsip transport manager.
+ */
+static pj_status_t ws_destroy(pjsip_transport *transport)
+{
+	struct ws_transport *wstransport = (struct ws_transport *)transport;
+
+	if (wstransport->transport.ref_cnt) {
+		pj_atomic_destroy(wstransport->transport.ref_cnt);
+	}
+
+	if (wstransport->transport.lock) {
+		pj_lock_destroy(wstransport->transport.lock);
+	}
+
+	pjsip_endpt_release_pool(wstransport->transport.endpt, wstransport->transport.pool);
+
+	return PJ_SUCCESS;
+}
+
+static int transport_shutdown(void *data)
+{
+	RAII_VAR(struct ast_sip_contact_transport *, ct, NULL, ao2_cleanup);
+	pjsip_transport *transport = data;
+
+	if ((ct = ast_sip_location_retrieve_contact_transport_by_transport(transport))) {
+		ast_sip_location_delete_contact_transport(ct);
+	}
+
+	pjsip_transport_shutdown(transport);
+	return 0;
+}
+
+struct transport_create_data {
+	struct ws_transport *transport;
+	struct ast_websocket *ws_session;
+};
+
+/*!
+ * \brief Create a pjsip transport.
+ */
+static int transport_create(void *data)
+{
+	struct transport_create_data *create_data = data;
+	struct ws_transport *newtransport;
+
+	pjsip_endpoint *endpt = ast_sip_get_pjsip_endpoint();
+	struct pjsip_tpmgr *tpmgr = pjsip_endpt_get_tpmgr(endpt);
+
+	pj_pool_t *pool;
+
+	pj_str_t buf;
+
+	if (!(pool = pjsip_endpt_create_pool(endpt, "ws", 512, 512))) {
+		ast_log(LOG_ERROR, "Failed to allocate WebSocket endpoint pool.\n");
+		return -1;
+	}
+
+	if (!(newtransport = PJ_POOL_ZALLOC_T(pool, struct ws_transport))) {
+		ast_log(LOG_ERROR, "Failed to allocate WebSocket transport.\n");
+		pjsip_endpt_release_pool(endpt, pool);
+		return -1;
+	}
+
+	newtransport->ws_session = create_data->ws_session;
+
+	pj_atomic_create(pool, 0, &newtransport->transport.ref_cnt);
+	pj_lock_create_recursive_mutex(pool, pool->obj_name, &newtransport->transport.lock);
+
+	newtransport->transport.pool = pool;
+	pj_sockaddr_parse(pj_AF_UNSPEC(), 0, pj_cstr(&buf, ast_sockaddr_stringify(ast_websocket_remote_address(newtransport->ws_session))), &newtransport->transport.key.rem_addr);
+	newtransport->transport.key.rem_addr.addr.sa_family = pj_AF_INET();
+	newtransport->transport.key.type = ast_websocket_is_secure(newtransport->ws_session) ? transport_type_wss : transport_type_ws;
+
+	newtransport->transport.addr_len = pj_sockaddr_get_len(&newtransport->transport.key.rem_addr);
+
+	pj_sockaddr_cp(&newtransport->transport.local_addr, &newtransport->transport.key.rem_addr);
+
+	newtransport->transport.local_name.host.ptr = (char *)pj_pool_alloc(pool, newtransport->transport.addr_len+4);
+	pj_sockaddr_print(&newtransport->transport.key.rem_addr, newtransport->transport.local_name.host.ptr, newtransport->transport.addr_len+4, 0);
+	newtransport->transport.local_name.host.slen = pj_ansi_strlen(newtransport->transport.local_name.host.ptr);
+	newtransport->transport.local_name.port = pj_sockaddr_get_port(&newtransport->transport.key.rem_addr);
+
+	newtransport->transport.type_name = (char *)pjsip_transport_get_type_name(newtransport->transport.key.type);
+	newtransport->transport.flag = pjsip_transport_get_flag_from_type((pjsip_transport_type_e)newtransport->transport.key.type);
+	newtransport->transport.info = (char *)pj_pool_alloc(newtransport->transport.pool, 64);
+
+	newtransport->transport.endpt = endpt;
+	newtransport->transport.tpmgr = tpmgr;
+	newtransport->transport.send_msg = &ws_send_msg;
+	newtransport->transport.destroy = &ws_destroy;
+
+	pjsip_transport_register(newtransport->transport.tpmgr, (pjsip_transport *)newtransport);
+
+	create_data->transport = newtransport;
+	return 0;
+}
+
+struct transport_read_data {
+	struct ws_transport *transport;
+	char *payload;
+	uint64_t payload_len;
+};
+
+/*!
+ * \brief Pass WebSocket data into pjsip transport manager.
+ */
+static int transport_read(void *data)
+{
+	struct transport_read_data *read_data = data;
+	struct ws_transport *newtransport = read_data->transport;
+	struct ast_websocket *session = newtransport->ws_session;
+
+	pjsip_rx_data *rdata = &newtransport->rdata;
+	int recvd;
+	pj_str_t buf;
+
+	rdata->tp_info.pool = newtransport->transport.pool;
+	rdata->tp_info.transport = &newtransport->transport;
+
+	pj_gettimeofday(&rdata->pkt_info.timestamp);
+
+	pj_memcpy(rdata->pkt_info.packet, read_data->payload, sizeof(rdata->pkt_info.packet));
+	rdata->pkt_info.len = read_data->payload_len;
+	rdata->pkt_info.zero = 0;
+
+	pj_sockaddr_parse(pj_AF_UNSPEC(), 0, pj_cstr(&buf, ast_sockaddr_stringify(ast_websocket_remote_address(session))), &rdata->pkt_info.src_addr);
+	rdata->pkt_info.src_addr.addr.sa_family = pj_AF_INET();
+
+	rdata->pkt_info.src_addr_len = sizeof(rdata->pkt_info.src_addr);
+
+	pj_ansi_strcpy(rdata->pkt_info.src_name, ast_sockaddr_stringify_host(ast_websocket_remote_address(session)));
+	rdata->pkt_info.src_port = ast_sockaddr_port(ast_websocket_remote_address(session));
+
+	recvd = pjsip_tpmgr_receive_packet(rdata->tp_info.transport->tpmgr, rdata);
+
+	return (read_data->payload_len == recvd) ? 0 : -1;
+}
+
+/*!
+ \brief WebSocket connection handler.
+ */
+static void websocket_cb(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers)
+{
+	struct ast_taskprocessor *serializer = NULL;
+	struct transport_create_data create_data;
+	struct ws_transport *transport = NULL;
+
+	if (ast_websocket_set_nonblock(session)) {
+		ast_websocket_unref(session);
+		return;
+	}
+
+	if (!(serializer = ast_sip_create_serializer())) {
+		ast_websocket_unref(session);
+		return;
+	}
+
+	create_data.ws_session = session;
+
+	if (ast_sip_push_task_synchronous(serializer, transport_create, &create_data)) {
+		ast_log(LOG_ERROR, "Could not create WebSocket transport.\n");
+		ast_websocket_unref(session);
+		return;
+	}
+
+	transport = create_data.transport;
+
+	while (ast_wait_for_input(ast_websocket_fd(session), -1) > 0) {
+		struct transport_read_data read_data;
+		enum ast_websocket_opcode opcode;
+		int fragmented;
+
+		if (ast_websocket_read(session, &read_data.payload, &read_data.payload_len, &opcode, &fragmented)) {
+			break;
+		}
+
+		if (opcode == AST_WEBSOCKET_OPCODE_TEXT || opcode == AST_WEBSOCKET_OPCODE_BINARY) {
+			read_data.transport = transport;
+
+			ast_sip_push_task(serializer, transport_read, &read_data);
+		} else if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
+			break;
+		}
+	}
+
+	ast_sip_push_task_synchronous(serializer, transport_shutdown, transport);
+
+	ast_taskprocessor_unreference(serializer);
+	ast_websocket_unref(session);
+}
+
+/*!
+ * \brief Session supplement handler for avoiding DNS lookup on bogus address.
+ */
+static void websocket_outgoing_request(struct ast_sip_session *session, struct pjsip_tx_data *tdata)
+{
+	char contact_uri[PJSIP_MAX_URL_SIZE] = { 0, };
+	RAII_VAR(struct ast_sip_contact_transport *, ct, NULL, ao2_cleanup);
+	pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_TRANSPORT, };
+
+	const pjsip_sip_uri *request_uri = pjsip_uri_get_uri(tdata->msg->line.req.uri);
+
+	if (pj_stricmp2(&request_uri->transport_param, "WS") && pj_stricmp2(&request_uri->transport_param, "WSS")) {
+		return;
+	}
+
+	pjsip_uri_print(PJSIP_URI_IN_REQ_URI, request_uri, contact_uri, sizeof(contact_uri));
+
+	if (!(ct = ast_sip_location_retrieve_contact_transport_by_uri(contact_uri))) {
+		return;
+	}
+
+	selector.u.transport = ct->transport;
+
+	pjsip_tx_data_set_transport(tdata, &selector);
+
+	tdata->dest_info.addr.count = 1;
+	tdata->dest_info.addr.entry[0].type = ct->transport->key.type;
+	tdata->dest_info.addr.entry[0].addr = ct->transport->key.rem_addr;
+	tdata->dest_info.addr.entry[0].addr_len = ct->transport->addr_len;
+}
+
+static struct ast_sip_session_supplement websocket_supplement = {
+	.outgoing_request = websocket_outgoing_request,
+};
+
+/*!
+ * \brief Destructor for ast_sip_contact_transport
+ */
+static void contact_transport_destroy(void *obj)
+{
+	struct ast_sip_contact_transport *ct = obj;
+
+	ast_string_field_free_memory(ct);
+}
+
+static void *contact_transport_alloc(void)
+{
+	struct ast_sip_contact_transport *ct = ao2_alloc(sizeof(*ct), contact_transport_destroy);
+
+	if (!ct) {
+		return NULL;
+	}
+
+	if (ast_string_field_init(ct, 256)) {
+		ao2_cleanup(ct);
+		return NULL;
+	}
+
+	return ct;
+}
+
+/*!
+ * \brief Store the transport a message came in on, so it can be used for outbound messages to that contact.
+ */
+static pj_bool_t websocket_on_rx_msg(pjsip_rx_data *rdata)
+{
+	pjsip_contact_hdr *contact_hdr = NULL;
+
+	long type = rdata->tp_info.transport->key.type;
+
+	if (type != (long)transport_type_ws && type != (long)transport_type_wss) {
+		return PJ_FALSE;
+	}
+
+	if ((contact_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, NULL))) {
+		RAII_VAR(struct ast_sip_contact_transport *, ct, NULL, ao2_cleanup);
+		char contact_uri[PJSIP_MAX_URL_SIZE];
+
+		pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, pjsip_uri_get_uri(contact_hdr->uri), contact_uri, sizeof(contact_uri));
+
+		if (!(ct = ast_sip_location_retrieve_contact_transport_by_uri(contact_uri))) {
+			if (!(ct = contact_transport_alloc())) {
+				return PJ_FALSE;
+			}
+
+			ast_string_field_set(ct, uri, contact_uri);
+			ct->transport = rdata->tp_info.transport;
+
+			ast_sip_location_add_contact_transport(ct);
+		}
+	}
+
+	return PJ_FALSE;
+}
+
+static pjsip_module websocket_module = {
+	.name = { "WebSocket Transport Module", 26 },
+	.id = -1,
+	.priority = PJSIP_MOD_PRIORITY_TRANSPORT_LAYER,
+	.on_rx_request = websocket_on_rx_msg,
+};
+
+static int load_module(void)
+{
+	pjsip_transport_register_type(PJSIP_TRANSPORT_RELIABLE, "WS", 5060, &transport_type_ws);
+	pjsip_transport_register_type(PJSIP_TRANSPORT_RELIABLE, "WSS", 5060, &transport_type_wss);
+
+	if (ast_sip_register_service(&websocket_module) != PJ_SUCCESS) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	ast_sip_session_register_supplement(&websocket_supplement);
+
+	if (ast_websocket_add_protocol("sip", websocket_cb)) {
+		ast_sip_unregister_service(&websocket_module);
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	ast_sip_unregister_service(&websocket_module);
+	ast_sip_session_unregister_supplement(&websocket_supplement);
+	ast_websocket_remove_protocol("sip", websocket_cb);
+
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "SIP WebSocket Transport Support",
+		.load = load_module,
+		.unload = unload_module,
+		.load_pri = AST_MODPRI_APP_DEPEND,
+	   );
-- 
GitLab