diff --git a/channels/chan_gulp.c b/channels/chan_gulp.c
index 9e939a0f44dbbaeb61e5977e8c530e01f7b51ec8..6a80651cf858627602dfbf148e93772fe82c505d 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 c207e24fec999682cf8175c920fb91366c4692f7..689b43a05a901620fc1394a3625263f3baa24bda 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 da1035e87852b445cbeff4343bf17c1312ec6160..0000000000000000000000000000000000000000
--- 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 0adde37f2024e6a8c91d4beb935b13b5624f565b..8b4672b25de7b6035886b4e6f9120c4895118bbb 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 a4ded62ca9e81813fd25ebe979345ef35f4a235b..0000000000000000000000000000000000000000
--- 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 8b2718fc3502314a2c7514189ce3814ea2eb23bb..0000000000000000000000000000000000000000
--- 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 dd8da8dffcce94a547ab872fd06838d987d6092c..34cd6d6458549bf6a24ec69f8c2cab7702e36e14 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 b48ed9f8218ad90f8401c40f18cd59c49ff1804f..7c486aa68956cc5dc851f8a77872c9bd78306cb4 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 0000000000000000000000000000000000000000..62662f9308b0a86bfed3172d61ca0539f2d699df
--- /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 33614b2853c1aa054da3a56d60de47fd600661c4..be443299ca2990d7268990ffd679b3bf9ba9011a 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 cbed5262108fe9fb95af67a26e2b2d0b16e4d40c..e4b05f7c3e3f5ef20cd18c8e5d9cf950e85ba1af 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 0000000000000000000000000000000000000000..9b92e0e3f0f6373c7c762c278a0e49c33417b1fe
--- /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 af988dcf961efa46afcf781f5462309e2ff70666..db737327f71770e84380fd63c3c21bc80c375a5e 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 c27e882c277696f4145dc2a3b3d921c266bc8700..85dc108a6097e25b5eb4fd48b5a76f4eab1dbe90 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 1af3fb78e5ee5d81046dda04d2b031b07c442ef5..ebbf596de50ec670151e659936f0575aeedcb536 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 010f90cb1db9b80f227e1dcb106670b0cfca4f84..625a02f7ec7f17c7fb7f22c292b317d9a7ee87c0 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 0df8c66adf329ca1913a4866a205a77d4436832d..1d60274b79e3bbb83cee2ad589dfdf1703bf6b79 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 318510aae0254d3c2fd89dc14c2d1ab4e57fe45d..3625bab3138fd588e6743c59f277c637fcb24c4e 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 91521c813575ce87fd1cd218a23c4500d49a8544..d0b0a28c95c8e0d3e5c8743f254380fbe42a917d 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 0000000000000000000000000000000000000000..068e8551ffb5a03fe2b1dcc0560d13bc20a8af39
--- /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 3488d527e050dfaeb795a77f593b91318b61c881..5864bdeecc5143f8ecd22d9e79082453de69d561 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 7662610897755217f83f0b34681558ea44481e2a..db36b61821fd25e4a353f3835567ff0518e15d27 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 5e3f8edcacf584e69b42ec476fa0dda21c52bedb..4c8a9f6a7936b88f80cdaf51678efc3bab6b34a9 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 22ece0436e46d390703552c2c75d7f61f1f04329..2f40473512519078f7f9bcc3506da7d16174fd2a 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 0000000000000000000000000000000000000000..70b1fc508453fc246e6f11867305ea3b3b54d078
--- /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 c8b03d5098823b6a2025a14fd3feb27e8d54ba2f..1954c695eaed35e2de31ee598aea94c72483ee53 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 0000000000000000000000000000000000000000..6f947e1a1429a43a99d7e4ab02e7af28c845c5e0
--- /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 0000000000000000000000000000000000000000..069343439f118d59fe63b1c5ea7590f694b9c790
--- /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 0000000000000000000000000000000000000000..0cce6f6dda223d89a44b47579fc90fc0a6fa5e61
--- /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 0000000000000000000000000000000000000000..10d47047f15eaa42cb266310bacb9db6c2dc0e78
--- /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 0000000000000000000000000000000000000000..b574b304c234178d128b4176178534a794efb168
--- /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 9d73f37d55ce2658b67f28ef71442e2f94376e42..203ecfc5a863359f74b4ef008493464ef688350c 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 0000000000000000000000000000000000000000..78633da9a5a52b70b5f79f6cece4a6023cafb76c
--- /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 c8a76a602bd2a97ac49e1fccaae5e87a1d5e5feb..590c96c49ac79f8bcd32468d8c4669ab7de983cb 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 55308746a817bafaa5841ce3c9698e2a9fb51e1a..0ef193d8a65f332d4c6d7dbf6edfd0de168a580a 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 0000000000000000000000000000000000000000..dfc35a3f4b3520ff6734b1661eae7c14acdc201c
--- /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 c3315d0becda2c2979290c49e1f7bf5db36a5896..7661f8d9377a5453f899b063530c2a087ddd9e94 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 0000000000000000000000000000000000000000..bbfa7e118199582f8558fc1eb5023903b182e94d
--- /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 b0c8ae31caf5ad3fd286ee1d47136992ac981ce4..bc150ed4a2d99be7438aa9b06cae9eb11e667f27 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 7be75ab1d88311d42f525108ebfb46e36c6c949f..9668b73e9ee1d0c60d1531d03d90cf0467931952 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 08c6f3937933b5f8195f4b1574b2b1ec971e1e79..28ed0b23998c6aef773349500e199eed3c66065c 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 0000000000000000000000000000000000000000..e83011cfcc91b18b58f2c55c7875f69ffc2e7b7f
--- /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,
+	   );