diff --git a/CREDITS b/CREDITS
index a7673963cafa4b634531dfc69b1df56963c2f4e3..65609768b0b778f184bac1bc95d1862af2a87d9e 100644
--- a/CREDITS
+++ b/CREDITS
@@ -276,6 +276,8 @@
 	* Andrew "lathama" Latham <lathama at gmail dot com>
 		Doxygen, HTTP-Static, Phoneprov, make update
 
+	* George Joseph - PJSIP CLI commands, PJSIP_HEADER dialplan function
+
 === OTHER CONTRIBUTIONS ===
 
  We'd like to thank the following for their listed contributions.
diff --git a/include/asterisk/config.h b/include/asterisk/config.h
index d669e7a81e8ec78c469d825d339bb51a0d5d3a5c..6d341db612d8372cc877f20fbe5b5a9f719eb836 100644
--- a/include/asterisk/config.h
+++ b/include/asterisk/config.h
@@ -691,6 +691,7 @@ void ast_include_rename(struct ast_config *conf, const char *from_file, const ch
 void ast_variable_append(struct ast_category *category, struct ast_variable *variable);
 void ast_variable_insert(struct ast_category *category, struct ast_variable *variable, const char *line);
 int ast_variable_delete(struct ast_category *category, const char *variable, const char *match, const char *line);
+struct ast_variable *ast_variable_list_sort(struct ast_variable *start);
 
 /*!
  * \brief Update variable value within a config
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index b3701c0445d8128b497ebd436d17c15836dd00cc..f4f9ba7c71585bd9ea27b047179a9efe26389924 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -40,6 +40,9 @@
 #include "asterisk/rtp_engine.h"
 /* Needed for AST_VECTOR macro */
 #include "asterisk/vector.h"
+/* Needed for ast_sip_for_each_channel_snapshot struct */
+#include "asterisk/stasis_channels.h"
+#include "asterisk/stasis_endpoints.h"
 
 /* Forward declarations of PJSIP stuff */
 struct pjsip_rx_data;
@@ -213,6 +216,17 @@ struct ast_sip_aor {
 	struct ao2_container *permanent_contacts;
 };
 
+/*!
+ * \brief Aor/Contact pair used for ast_sip_for_each_contact callback.
+ */
+struct ast_sip_aor_contact_pair {
+	SORCERY_OBJECT(details);
+	/*! Aor */
+	struct ast_sip_aor *aor;
+	/*! Contact */
+	struct ast_sip_contact *contact;
+};
+
 /*!
  * \brief DTMF modes for SIP endpoints
  */
@@ -1546,13 +1560,6 @@ void *ast_sip_dict_set(pj_pool_t* pool, void *ht,
 #define ast_sip_mod_data_set(pool, mod_data, id, key, val)		\
 	mod_data[id] = ast_sip_dict_set(pool, mod_data[id], key, val)
 
-/*!
- * \brief Function pointer for contact callbacks.
- */
-typedef int (*on_contact_t)(const struct ast_sip_aor *aor,
-			    const struct ast_sip_contact *contact,
-			    int last, void *arg);
-
 /*!
  * \brief For every contact on an AOR call the given 'on_contact' handler.
  *
@@ -1561,21 +1568,18 @@ typedef int (*on_contact_t)(const struct ast_sip_aor *aor,
  * \param arg user data passed to handler
  * \retval 0 Success, non-zero on failure
  */
-int ast_sip_for_each_contact(const struct ast_sip_aor *aor,
-			     on_contact_t on_contact, void *arg);
+int ast_sip_for_each_contact(struct ast_sip_aor *aor,
+		ao2_callback_fn on_contact, void *arg);
 
 /*!
  * \brief Handler used to convert a contact to a string.
  *
- * \param aor the aor containing a list of contacts to iterate
- * \param contact the contact to convert
- * \param last is this the last contact
+ * \param object the ast_sip_aor_contact_pair containing a list of contacts to iterate and the contact
  * \param arg user data passed to handler
+ * \param flags
  * \retval 0 Success, non-zero on failure
  */
-int ast_sip_contact_to_str(const struct ast_sip_aor *aor,
-			   const struct ast_sip_contact *contact,
-			   int last, void *arg);
+int ast_sip_contact_to_str(void *object, void *arg, int flags);
 
 /*!
  * \brief For every aor in the comma separated aors string call the
@@ -1696,4 +1700,47 @@ int ast_sip_format_endpoint_ami(struct ast_sip_endpoint *endpoint,
 int ast_sip_format_auths_ami(const struct ast_sip_auth_vector *auths,
 			     struct ast_sip_ami *ami);
 
+/*!
+ * \brief Retrieve the endpoint snapshot for an endpoint
+ *
+ * \param endpoint The endpoint whose snapshot is to be retreieved.
+ * \retval The endpoint snapshot
+ */
+struct ast_endpoint_snapshot *ast_sip_get_endpoint_snapshot(
+	const struct ast_sip_endpoint *endpoint);
+
+/*!
+ * \brief Retrieve the device state for an endpoint.
+ *
+ * \param endpoint The endpoint whose state is to be retrieved.
+ * \retval The device state.
+ */
+const char *ast_sip_get_device_state(const struct ast_sip_endpoint *endpoint);
+
+/*!
+ * \brief For every channel snapshot on an endpoint snapshot call the given
+ *        'on_channel_snapshot' handler.
+ *
+ * \param endpoint_snapshot snapshot of an endpoint
+ * \param on_channel_snapshot callback for each channel snapshot
+ * \param arg user data passed to handler
+ * \retval 0 Success, non-zero on failure
+ */
+int ast_sip_for_each_channel_snapshot(const struct ast_endpoint_snapshot *endpoint_snapshot,
+		ao2_callback_fn on_channel_snapshot,
+				      void *arg);
+
+/*!
+ * \brief For every channel snapshot on an endpoint all the given
+ *        'on_channel_snapshot' handler.
+ *
+ * \param endpoint endpoint
+ * \param on_channel_snapshot callback for each channel snapshot
+ * \param arg user data passed to handler
+ * \retval 0 Success, non-zero on failure
+ */
+int ast_sip_for_each_channel(const struct ast_sip_endpoint *endpoint,
+		ao2_callback_fn on_channel_snapshot,
+				      void *arg);
+
 #endif /* _RES_PJSIP_H */
diff --git a/include/asterisk/res_pjsip_cli.h b/include/asterisk/res_pjsip_cli.h
new file mode 100644
index 0000000000000000000000000000000000000000..cd7d81ed299756bc9607d5e1b48cc1d6ef9445d7
--- /dev/null
+++ b/include/asterisk/res_pjsip_cli.h
@@ -0,0 +1,95 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Fairview 5 Engineering, LLC.
+ *
+ * George Joseph <george.joseph@fairview5.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_PJSIP_CLI_H_
+#define RES_PJSIP_CLI_H_
+
+#define CLI_HEADER_FILLER ".........................................................................................."
+#define CLI_DETAIL_FILLER "                                                                                          "
+#define CLI_MAX_WIDTH 90
+#define CLI_LAST_TABSTOP 62
+#define CLI_MAX_TITLE_NAME 8
+#define CLI_INDENT_TO_SPACES(x) ((x * 2) + 1 + CLI_MAX_TITLE_NAME)
+
+/*
+ * \brief CLI Formatter Context
+ */
+struct ast_sip_cli_context {
+	int peers_mon_online;
+	int peers_mon_offline;
+	int peers_unmon_offline;
+	int peers_unmon_online;
+	struct ast_str *output_buffer;
+	const struct ast_cli_args *a;
+	const struct ast_sip_endpoint *current_endpoint;
+	const struct ast_sip_auth *current_auth;
+	const struct ast_sip_aor *current_aor;
+	char *auth_direction;
+	unsigned int print_flags;
+	int indent_level;
+	unsigned show_details : 1;
+	unsigned recurse : 1;
+	unsigned show_details_only_level_0 : 1;
+};
+
+/*
+ * \brief CLI Formatter Registry Entry
+ */
+struct ast_sip_cli_formatter_entry {
+	const char *name;
+	ao2_callback_fn *print_header;
+	ao2_callback_fn *print_body;
+	struct ao2_container *(* get_container)(struct ast_sorcery *);
+};
+
+/*!
+ * \brief Registers a CLI formatter.
+ *
+ * \param name The name of the formatter, usually the sorcery object type.
+ * \param formatter An ao2_callback_fn that outputs the formatted data.
+ * \retval 0 Success, non-zero on failure
+ */
+int ast_sip_register_cli_formatter(struct ast_sip_cli_formatter_entry *formatter);
+
+/*!
+ * \brief Unregisters a CLI formatter.
+ *
+ * \param name The name of the formatter, usually the sorcery object type.
+ * \retval 0 Success, non-zero on failure
+ */
+int ast_sip_unregister_cli_formatter(struct ast_sip_cli_formatter_entry *formatter);
+
+/*!
+ * \brief Looks up a CLI formatter by type.
+ *
+ * \param name The name of the formatter, usually the sorcery object type.
+ * \retval Pointer to formatter entry structure
+ */
+struct ast_sip_cli_formatter_entry *ast_sip_lookup_cli_formatter(const char *name);
+
+/*!
+ * \brief Prints a sorcery object's ast_variable list
+ *
+ * \param obj The sorcery object
+ * \param arg The ast_sip_cli_context.
+ * \retval 0 Success, non-zero on failure
+ */
+int ast_sip_cli_print_sorcery_objectset(void *obj, void *arg, int flags);
+
+
+#endif /* RES_PJSIP_CLI_H_ */
diff --git a/include/asterisk/sorcery.h b/include/asterisk/sorcery.h
index 1402c583394ea1829d5e5914ace83f97022fa39d..47ad5e40f70bc9fafa304124cb6d110f8c05066b 100644
--- a/include/asterisk/sorcery.h
+++ b/include/asterisk/sorcery.h
@@ -823,6 +823,12 @@ const char *ast_sorcery_object_get_extended(const void *object, const char *name
  */
 int ast_sorcery_object_set_extended(const void *object, const char *name, const char *value);
 
+/*!
+ * \brief Sorcery object comparator based on id.
+ */
+int ast_sorcery_object_id_compare(const void *obj_left, const void *obj_right, int flags);
+
+
 #if defined(__cplusplus) || defined(c_plusplus)
 }
 #endif
diff --git a/main/channel.c b/main/channel.c
index 4789943c042269d529911c9066672714d4ebcb17..9ff3d0746ee05efb18e31ae5e3d666f06980b582 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -733,6 +733,8 @@ const char *ast_state2str(enum ast_channel_state state)
 		return "Dialing Offhook";
 	case AST_STATE_PRERING:
 		return "Pre-ring";
+	case AST_STATE_MUTE:
+		return "Mute";
 	default:
 		if (!(buf = ast_threadstorage_get(&state2str_threadbuf, STATE2STR_BUFSIZE)))
 			return "Unknown";
diff --git a/main/config.c b/main/config.c
index b8356a9bc9ebd224e239e7b23cfa9a19e269e36d..c6458caafbb55dfd6d8a2662ac646b34bf48d5a4 100644
--- a/main/config.c
+++ b/main/config.c
@@ -70,6 +70,7 @@ static char *extconfig_conf = "extconfig.conf";
 
 static struct ao2_container *cfg_hooks;
 static void config_hook_exec(const char *filename, const char *module, struct ast_config *cfg);
+inline struct ast_variable *variable_list_switch(struct ast_variable *l1, struct ast_variable *l2);
 
 /*! \brief Structure to keep comments for rewriting configuration files */
 struct ast_comment {
@@ -581,6 +582,39 @@ struct ast_variable *ast_variable_browse(const struct ast_config *config, const
 	return (cat) ? cat->root : NULL;
 }
 
+inline struct ast_variable *variable_list_switch(struct ast_variable *l1, struct ast_variable *l2)
+{
+    l1->next = l2->next;
+    l2->next = l1;
+    return l2;
+}
+
+struct ast_variable *ast_variable_list_sort(struct ast_variable *start)
+{
+	struct ast_variable *p, *q, *top;
+	int changed = 1;
+	top = ast_calloc(1, sizeof(struct ast_variable));
+	top->next = start;
+	if (start != NULL && start->next != NULL) {
+		while (changed) {
+			changed = 0;
+			q = top;
+			p = top->next;
+			while (p->next != NULL) {
+				if (p->next != NULL && strcmp(p->name, p->next->name) > 0) {
+					q->next = variable_list_switch(p, p->next);
+
+					changed = 1;
+				}
+				q = p;
+				if (p->next != NULL)
+					p = p->next;
+			}
+		}
+	}
+	return top->next;
+}
+
 const char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var)
 {
 	const char *tmp;
diff --git a/main/sorcery.c b/main/sorcery.c
index c7e1a03f36f5bd152bb2b3c66cc699beadfcea9f..1753ba198a0ac4d659a2a9aaba18f37ab4ae9805 100644
--- a/main/sorcery.c
+++ b/main/sorcery.c
@@ -1583,3 +1583,11 @@ void ast_sorcery_observer_remove(const struct ast_sorcery *sorcery, const char *
 	ao2_callback(object_type->observers, OBJ_NODATA | OBJ_UNLINK,
 		sorcery_observer_remove, cbs);
 }
+
+int ast_sorcery_object_id_compare(const void *obj_left, const void *obj_right, int flags)
+{
+	if (!obj_left || !obj_right) {
+		return 0;
+	}
+	return strcmp(ast_sorcery_object_get_id(obj_left), ast_sorcery_object_get_id(obj_right));
+}
diff --git a/res/res_pjsip/config_auth.c b/res/res_pjsip/config_auth.c
index 047e9337d535f81f69a3c664706fe249b0e5a8b4..d7e759f5dcc9b57f8ee645a6ab215e4b456795b2 100644
--- a/res/res_pjsip/config_auth.c
+++ b/res/res_pjsip/config_auth.c
@@ -23,7 +23,9 @@
 #include "asterisk/res_pjsip.h"
 #include "asterisk/logger.h"
 #include "asterisk/sorcery.h"
+#include "asterisk/cli.h"
 #include "include/res_pjsip_private.h"
+#include "asterisk/res_pjsip_cli.h"
 
 static void auth_destroy(void *obj)
 {
@@ -197,6 +199,59 @@ static struct ast_sip_endpoint_formatter endpoint_auth_formatter = {
 	.format_ami = format_ami_endpoint_auth
 };
 
+static struct ao2_container *cli_get_auth_container(struct ast_sorcery *sip_sorcery)
+{
+	return ast_sorcery_retrieve_by_fields(sip_sorcery, "auth",
+				AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+}
+
+static int cli_print_auth_header(void *obj, void *arg, int flags) {
+	struct ast_sip_cli_context *context = arg;
+	int indent = CLI_INDENT_TO_SPACES(context->indent_level);
+	int filler = CLI_MAX_WIDTH - indent - 20;
+
+	if (!context->output_buffer) {
+		return -1;
+	}
+
+	ast_str_append(&context->output_buffer, 0,
+		"%*s:  <AuthId/UserName%*.*s>\n", indent, "I/OAuth", filler, filler, CLI_HEADER_FILLER);
+
+	return 0;
+}
+
+static int cli_print_auth_body(void *obj, void *arg, int flags) {
+	struct ast_sip_auth *auth = obj;
+	struct ast_sip_cli_context *context = arg;
+	char title[32];
+
+	context->current_auth = auth;
+
+	if (!context->output_buffer) {
+		return -1;
+	}
+
+	snprintf(title, 32, "%sAuth",context->auth_direction ? context->auth_direction : "");
+
+	ast_str_append(&context->output_buffer, 0, "%*s:  %s/%s\n",
+		CLI_INDENT_TO_SPACES(context->indent_level), title,
+		ast_sorcery_object_get_id(auth), auth->auth_user);
+
+	if (context->show_details || (context->show_details_only_level_0 && context->indent_level == 0)) {
+		ast_str_append(&context->output_buffer, 0, "\n");
+		ast_sip_cli_print_sorcery_objectset(auth, context, 0);
+	}
+
+	return 0;
+}
+
+static struct ast_sip_cli_formatter_entry  cli_auth_formatter = {
+	.name = SIP_SORCERY_AUTH_TYPE,
+	.print_header = cli_print_auth_header,
+	.print_body = cli_print_auth_body,
+	.get_container = cli_get_auth_container,
+};
+
 /*! \brief Initialize sorcery with auth support */
 int ast_sip_initialize_sorcery_auth(struct ast_sorcery *sorcery)
 {
@@ -222,5 +277,7 @@ int ast_sip_initialize_sorcery_auth(struct ast_sorcery *sorcery)
 			"userpass", auth_type_handler, auth_type_to_str, 0, 0);
 
 	ast_sip_register_endpoint_formatter(&endpoint_auth_formatter);
+	ast_sip_register_cli_formatter(&cli_auth_formatter);
+
 	return 0;
 }
diff --git a/res/res_pjsip/include/res_pjsip_private.h b/res/res_pjsip/include/res_pjsip_private.h
index 368bdd04a6fe9f353182864d85150fb4289fb22c..a2d1c6eb468b078fe9a63898a1c2d8280cfbd3be 100644
--- a/res/res_pjsip/include/res_pjsip_private.h
+++ b/res/res_pjsip/include/res_pjsip_private.h
@@ -9,8 +9,6 @@
 #define RES_PJSIP_PRIVATE_H_
 
 #include "asterisk/module.h"
-#include "asterisk/stasis_channels.h"
-#include "asterisk/stasis_endpoints.h"
 
 struct ao2_container;
 struct ast_threadpool_options;
@@ -86,25 +84,6 @@ void ast_res_pjsip_cleanup_options_handling(void);
  */
 void sip_get_threadpool_options(struct ast_threadpool_options *threadpool_options);
 
-/*!
- * \brief Function pointer for channel snapshot callbacks.
- */
-typedef int (*on_channel_snapshot_t)(
-	const struct ast_channel_snapshot *snapshot, int last, void *arg);
-
-/*!
- * \brief For every channel snapshot on an endpoint snapshot call the given
- *        'on_channel_snapshot' handler.
- *
- * \param endpoint_snapshot snapshot of an endpoint
- * \param on_channel_snapshot callback for each channel snapshot
- * \param arg user data passed to handler
- * \retval 0 Success, non-zero on failure
- */
-int ast_sip_for_each_channel_snapshot(const struct ast_endpoint_snapshot *endpoint_snapshot,
-				      on_channel_snapshot_t on_channel_snapshot,
-				      void *arg);
-
 /*!
  * \brief Retrieve the name of the default outbound endpoint.
  *
@@ -116,4 +95,10 @@ int ast_sip_for_each_channel_snapshot(const struct ast_endpoint_snapshot *endpoi
  */
 char *ast_sip_global_default_outbound_endpoint(void);
 
+/*!
+ * \brief Functions for initializing and destroying the CLI.
+ */
+int ast_sip_initialize_cli(struct ast_sorcery *sip_sorcery);
+void ast_sip_destroy_cli(void);
+
 #endif /* RES_PJSIP_PRIVATE_H_ */
diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c
index 52f69cfd8ed54e06714dfdcce6bf8345b048aa76..3bd4dea104e7dae4cd49c46062fe4a81a11d4c11 100644
--- a/res/res_pjsip/location.c
+++ b/res/res_pjsip/location.c
@@ -25,6 +25,7 @@
 #include "asterisk/astobj2.h"
 #include "asterisk/sorcery.h"
 #include "include/res_pjsip_private.h"
+#include "asterisk/res_pjsip_cli.h"
 
 /*! \brief Destructor for AOR */
 static void aor_destroy(void *obj)
@@ -284,12 +285,37 @@ int ast_sip_for_each_aor(const char *aors, ao2_callback_fn on_aor, void *arg)
 	return 0;
 }
 
-int ast_sip_for_each_contact(const struct ast_sip_aor *aor,
-			     on_contact_t on_contact, void *arg)
+static void destroy_contact_pair(void *obj)
+{
+	struct ast_sip_aor_contact_pair *pair = obj;
+	ao2_cleanup(pair->aor);
+	ao2_cleanup(pair->contact);
+}
+
+static struct ast_sip_aor_contact_pair *create_contact_pair(
+	struct ast_sip_aor *aor, struct ast_sip_contact *contact)
+{
+	struct ast_sip_aor_contact_pair *pair = ao2_alloc(
+		sizeof(*pair), destroy_contact_pair);
+
+	if (!pair) {
+		return NULL;
+	}
+
+	pair->aor = aor;
+	pair->contact = contact;
+
+	ao2_ref(pair->aor, +1);
+	ao2_ref(pair->contact, +1);
+
+	return pair;
+}
+
+int ast_sip_for_each_contact(struct ast_sip_aor *aor,
+		ao2_callback_fn on_contact, void *arg)
 {
 	RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
 	struct ast_sip_contact *contact;
-	int num;
 	struct ao2_iterator i;
 
 	if (!on_contact ||
@@ -297,13 +323,13 @@ int ast_sip_for_each_contact(const struct ast_sip_aor *aor,
 		return 0;
 	}
 
-	num = ao2_container_count(contacts);
 	i = ao2_iterator_init(contacts, 0);
 	while ((contact = ao2_iterator_next(&i))) {
-		int res = on_contact(aor, contact, --num == 0, arg);
+		int res;
+		RAII_VAR(struct ast_sip_aor_contact_pair *,
+			 acp, create_contact_pair(aor, contact), ao2_cleanup);
 
-		ao2_ref(contact, -1);
-		if (res) {
+		if (!acp || (res = on_contact(acp, arg, 0))) {
 			ao2_iterator_destroy(&i);
 			return -1;
 		}
@@ -312,18 +338,13 @@ int ast_sip_for_each_contact(const struct ast_sip_aor *aor,
 	return 0;
 }
 
-int ast_sip_contact_to_str(const struct ast_sip_aor *aor,
-			   const struct ast_sip_contact *contact,
-			   int last, void *arg)
+int ast_sip_contact_to_str(void *object, void *arg, int flags)
 {
+	struct ast_sip_aor_contact_pair *acp = object;
 	struct ast_str **buf = arg;
 
-	ast_str_append(buf, 0, "%s/%s",
-		       ast_sorcery_object_get_id(aor), contact->uri);
-
-	if (!last) {
-		ast_str_append(buf, 0, ",");
-	}
+	ast_str_append(buf, 0, "%s/%s,",
+		       ast_sorcery_object_get_id(acp->aor), acp->contact->uri);
 
 	return 0;
 }
@@ -335,7 +356,7 @@ static int sip_aor_to_ami(const struct ast_sip_aor *aor, struct ast_str **buf)
 
 static int format_ami_aor_handler(void *obj, void *arg, int flags)
 {
-	const struct ast_sip_aor *aor = obj;
+	struct ast_sip_aor *aor = obj;
 	struct ast_sip_ami *ami = arg;
 	const struct ast_sip_endpoint *endpoint = ami->arg;
 	RAII_VAR(struct ast_str *, buf,
@@ -352,6 +373,7 @@ static int format_ami_aor_handler(void *obj, void *arg, int flags)
 	sip_aor_to_ami(aor, &buf);
 	ast_str_append(&buf, 0, "Contacts: ");
 	ast_sip_for_each_contact(aor, ast_sip_contact_to_str, &buf);
+	ast_str_truncate(buf, -1);
 	ast_str_append(&buf, 0, "\r\n");
 
 	num = ao2_container_count(contacts);
@@ -377,6 +399,193 @@ struct ast_sip_endpoint_formatter endpoint_aor_formatter = {
 	.format_ami = format_ami_endpoint_aor
 };
 
+static int populate_contact_container(void *obj, void *arg, int flags)
+{
+	struct ast_sip_aor_contact_pair *acp = obj;
+	struct ao2_container *container = arg;
+	ao2_link_flags(container, acp, OBJ_NOLOCK);
+	return 0;
+}
+
+static int gather_aor_channels(void *obj, void *arg, int flags)
+{
+	struct ast_sip_aor *aor = obj;
+	struct ao2_container *container = arg;
+	ast_sip_for_each_contact(aor, populate_contact_container, container);
+	return 0;
+}
+
+static struct ao2_container *cli_get_contact_container(struct ast_sorcery *sip_sorcery)
+{
+	RAII_VAR(struct ao2_container *, parent_container, NULL, ao2_cleanup);
+	RAII_VAR(struct ao2_container *, s_parent_container, NULL, ao2_cleanup);
+	struct ao2_container *child_container;
+
+	parent_container = ast_sorcery_retrieve_by_fields(sip_sorcery, "aor",
+			AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+	if (!parent_container) {
+		return NULL;
+	}
+
+	s_parent_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, &ast_sorcery_object_id_compare, NULL);
+	if (!s_parent_container) {
+		return NULL;
+	}
+
+	ao2_container_dup(s_parent_container, parent_container, OBJ_ORDER_ASCENDING);
+
+	child_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, NULL, NULL);
+	if (!child_container) {
+		return NULL;
+	}
+
+	ao2_callback(s_parent_container, OBJ_NODATA, gather_aor_channels, child_container);
+
+	return child_container;
+}
+
+
+static int cli_print_contact_header(void *obj, void *arg, int flags)
+{
+	struct ast_sip_cli_context *context = arg;
+	int indent = CLI_INDENT_TO_SPACES(context->indent_level);
+	int filler = CLI_LAST_TABSTOP - indent - 18;
+
+	if (!context->output_buffer) {
+		return -1;
+	}
+	ast_str_append(&context->output_buffer, 0,
+		"%*s:  <Aor/ContactUri%*.*s>  <Status....>  <RTT(ms)..>\n",
+		indent, "Contact", filler, filler, CLI_HEADER_FILLER);
+
+	return 0;
+}
+
+static int cli_print_contact_body(void *obj, void *arg, int flags)
+{
+	struct ast_sip_aor_contact_pair *acp = obj;
+	struct ast_sip_cli_context *context = arg;
+	char *print_name = NULL;
+	int print_name_len;
+	int indent;
+	int flexwidth;
+
+	RAII_VAR(struct ast_sip_contact_status *, status,
+		ast_sorcery_retrieve_by_id( ast_sip_get_sorcery(), CONTACT_STATUS, ast_sorcery_object_get_id(acp->contact)),
+		ao2_cleanup);
+
+	if (!context->output_buffer) {
+		return -1;
+	}
+
+	print_name_len = strlen(ast_sorcery_object_get_id(acp->aor))
+		+ strlen(acp->contact->uri) + 2;
+	if (!(print_name = alloca(print_name_len))) {
+		return -1;
+	}
+	snprintf(print_name, print_name_len, "%s/%s",
+		ast_sorcery_object_get_id(acp->aor), acp->contact->uri);
+
+	indent = CLI_INDENT_TO_SPACES(context->indent_level);
+	flexwidth = CLI_LAST_TABSTOP - indent - 2;
+
+	ast_str_append(&context->output_buffer, 0, "%*s:  %-*.*s  %-12.12s  %11.3f\n",
+		indent,
+		"Contact",
+		flexwidth, flexwidth,
+		print_name,
+		(status ? (status->status == AVAILABLE ? "Avail" : "Unavail") : "Unknown"),
+		(status ? ((long long) status->rtt) / 1000.0 : NAN));
+
+	return 0;
+}
+
+static struct ao2_container *cli_get_aor_container(struct ast_sorcery *sip_sorcery)
+{
+	return ast_sorcery_retrieve_by_fields(sip_sorcery, "aor",
+				AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+}
+
+static int cli_print_aor_header(void *obj, void *arg, int flags)
+{
+	struct ast_sip_cli_context *context = arg;
+	struct ast_sip_cli_formatter_entry *formatter_entry;
+
+	int indent = CLI_INDENT_TO_SPACES(context->indent_level);
+	int filler = CLI_LAST_TABSTOP - indent - 7;
+
+	if (!context->output_buffer) {
+		return -1;
+	}
+	ast_str_append(&context->output_buffer, 0,
+		"%*s:  <Aor%*.*s>  <MaxContact>\n",
+		indent, "Aor", filler, filler, CLI_HEADER_FILLER);
+
+	if (context->recurse) {
+		context->indent_level++;
+		formatter_entry = ast_sip_lookup_cli_formatter("contact");
+		if (formatter_entry) {
+			formatter_entry->print_header(NULL, context, 0);
+		}
+		context->indent_level--;
+	}
+	return 0;
+}
+
+static int cli_print_aor_body(void *obj, void *arg, int flags)
+{
+	struct ast_sip_aor *aor = obj;
+	struct ast_sip_cli_context *context = arg;
+	struct ast_sip_cli_formatter_entry *formatter_entry;
+	int indent;
+	int flexwidth;
+
+	if (!context->output_buffer) {
+		return -1;
+	}
+
+	context->current_aor = aor;
+
+	indent = CLI_INDENT_TO_SPACES(context->indent_level);
+	flexwidth = CLI_LAST_TABSTOP - indent - 12;
+
+	ast_str_append(&context->output_buffer, 0, "%*s:  %-*.*s %12d\n",
+		indent,
+		"Aor",
+		flexwidth, flexwidth,
+		ast_sorcery_object_get_id(aor), aor->max_contacts);
+
+	if (context->recurse) {
+		context->indent_level++;
+		formatter_entry = ast_sip_lookup_cli_formatter("contact");
+		if (formatter_entry) {
+			ast_sip_for_each_contact(aor, formatter_entry->print_body, context);
+		}
+		context->indent_level--;
+	}
+
+	if (context->show_details || (context->show_details_only_level_0 && context->indent_level == 0)) {
+		ast_str_append(&context->output_buffer, 0, "\n");
+		ast_sip_cli_print_sorcery_objectset(aor, context, 0);
+	}
+
+	return 0;
+}
+
+static struct ast_sip_cli_formatter_entry cli_contact_formatter = {
+	.name = "contact",
+	.print_header = cli_print_contact_header,
+	.print_body = cli_print_contact_body,
+	.get_container = cli_get_contact_container,
+};
+
+static struct ast_sip_cli_formatter_entry cli_aor_formatter = {
+	.name = "aor",
+	.print_header = cli_print_aor_header,
+	.print_body = cli_print_aor_body,
+	.get_container = cli_get_aor_container,
+};
+
 /*! \brief Initialize sorcery with location support */
 int ast_sip_initialize_sorcery_location(struct ast_sorcery *sorcery)
 {
@@ -408,6 +617,8 @@ int ast_sip_initialize_sorcery_location(struct ast_sorcery *sorcery)
 	ast_sorcery_object_field_register(sorcery, "aor", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_aor, outbound_proxy));
 
 	ast_sip_register_endpoint_formatter(&endpoint_aor_formatter);
+	ast_sip_register_cli_formatter(&cli_contact_formatter);
+	ast_sip_register_cli_formatter(&cli_aor_formatter);
 	return 0;
 }
 
diff --git a/res/res_pjsip/pjsip_cli.c b/res/res_pjsip/pjsip_cli.c
new file mode 100644
index 0000000000000000000000000000000000000000..5f8157d2d8dbcc183215ac44c9bb066b8f2db1d8
--- /dev/null
+++ b/res/res_pjsip/pjsip_cli.c
@@ -0,0 +1,319 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Fairview 5 Engineering, LLC
+ *
+ * George Joseph <george.joseph@fairview5.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"
+
+#include <pjsip.h>
+#include <pjsip_ua.h>
+#include "asterisk/res_pjsip.h"
+#include "include/res_pjsip_private.h"
+#include "asterisk/res_pjsip_cli.h"
+#include "asterisk/acl.h"
+#include "asterisk/cli.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/hashtab.h"
+#include "asterisk/utils.h"
+#include "asterisk/sorcery.h"
+
+static struct ast_hashtab *formatter_registry;
+
+static struct ast_sorcery *sip_sorcery;
+
+struct ast_sip_cli_formatter_entry *ast_sip_lookup_cli_formatter(const char *name)
+{
+	struct ast_sip_cli_formatter_entry fake_entry = {
+		.name = name,
+	};
+	return ast_hashtab_lookup(formatter_registry, &fake_entry);
+}
+
+int ast_sip_cli_print_sorcery_objectset(void *obj, void *arg, int flags)
+{
+	struct ast_sip_cli_context *context = arg;
+	struct ast_variable *i;
+	int max_name_width = 13;
+	int max_value_width = 14;
+	int width;
+	char *separator;
+	struct ast_variable *objset;
+
+	if (!context->output_buffer) {
+		return -1;
+	}
+
+	objset = ast_sorcery_objectset_create(ast_sip_get_sorcery(),obj);
+	if (!objset) {
+		return -1;
+	}
+
+	for (i = objset; i; i = i->next) {
+		if (i->name) {
+			width = strlen(i->name);
+			max_name_width = width > max_name_width ? width : max_name_width;
+		}
+		if (i->value) {
+			width = strlen(i->value);
+			max_value_width = width > max_value_width ? width : max_value_width;
+		}
+	}
+
+	if (!(separator = alloca(max_name_width + max_value_width + 8))) {
+		return -1;
+	}
+	memset(separator, '=', max_name_width + max_value_width + 3);
+	separator[max_name_width + max_value_width + 3] = 0;
+
+	ast_str_append(&context->output_buffer, 0, " %-*s : %s\n", max_name_width, "ParameterName", "ParameterValue");
+	ast_str_append(&context->output_buffer, 0, " %s\n", separator);
+
+	objset = ast_variable_list_sort(objset);
+
+	for (i = objset; i; i = i->next) {
+		ast_str_append(&context->output_buffer, 0, " %-*s : %s\n", max_name_width, i->name, i->value);
+	}
+
+	return 0;
+}
+
+static char *complete_show_sorcery_object(struct ao2_container *container,
+	const char *word, int state)
+{
+	char *result = NULL;
+	int wordlen = strlen(word);
+	int which = 0;
+
+	struct ao2_iterator i = ao2_iterator_init(container, 0);
+	void *object;
+
+	while ((object = ao2_t_iterator_next(&i, "iterate thru endpoints table"))) {
+		if (!strncasecmp(word, ast_sorcery_object_get_id(object), wordlen)
+			&& ++which > state) {
+			result = ast_strdup(ast_sorcery_object_get_id(object));
+		}
+		ao2_t_ref(object, -1, "toss iterator endpoint ptr before break");
+		if (result) {
+			break;
+		}
+	}
+	ao2_iterator_destroy(&i);
+	return result;
+}
+
+static void dump_str_and_free(int fd, struct ast_str *buf) {
+	ast_cli(fd, "%s", ast_str_buffer(buf));
+	ast_free(buf);
+}
+
+static char *cli_traverse_objects(struct ast_cli_entry *e, int cmd,
+	struct ast_cli_args *a)
+{
+	RAII_VAR(struct ao2_container *, container, NULL, ao2_cleanup);
+	RAII_VAR(struct ao2_container *, s_container, NULL, ao2_cleanup);
+	RAII_VAR(void *, object, NULL, ao2_cleanup);
+	int is_container = 0;
+	const char *cmd1 = NULL;
+	const char *cmd2 = NULL;
+	const char *object_id = NULL;
+	char formatter_type[64];
+	struct ast_sip_cli_formatter_entry *formatter_entry;
+	int is_plural = 0;
+
+	struct ast_sip_cli_context context = {
+		.peers_mon_online = 0,
+		.peers_mon_offline = 0,
+		.peers_unmon_online = 0,
+		.peers_unmon_offline = 0,
+		.a = a,
+		.indent_level = 0,
+	};
+
+	if (cmd == CLI_INIT) {
+		return NULL;
+	}
+
+	cmd1 = e->cmda[1];
+	cmd2 = e->cmda[2];
+	object_id = a->argv[3];
+
+	if (!ast_ends_with(cmd2, "s")) {
+		ast_copy_string(formatter_type, cmd2, strlen(cmd2)+1);
+		is_plural = 0;
+	} else {
+		ast_copy_string(formatter_type, cmd2, strlen(cmd2));
+		is_plural = 1;
+	}
+
+	if (!strcmp(cmd1, "show")) {
+		if (is_plural) {
+			context.recurse = 1;
+			context.show_details = 0;
+			context.show_details_only_level_0 = 0;
+			is_container = 1;
+		} else {
+			context.recurse = 1;
+			context.show_details = 0;
+			context.show_details_only_level_0 = 1;
+			is_container = 0;
+		}
+	} else {
+		context.recurse = 0;
+		context.show_details = 0;
+		context.show_details_only_level_0 = 0;
+		is_container = 1;
+	}
+
+	context.output_buffer = ast_str_create(256);
+	formatter_entry = ast_sip_lookup_cli_formatter(formatter_type);
+	if (!formatter_entry) {
+		ast_log(LOG_ERROR, "CLI TRAVERSE failure.  No container found for object type %s\n", formatter_type);
+		return CLI_FAILURE;
+	}
+	ast_str_append(&context.output_buffer, 0, "\n");
+	formatter_entry->print_header(NULL, &context, 0);
+	ast_str_append(&context.output_buffer, 0, " =========================================================================================\n\n");
+
+	if (is_container || cmd == CLI_GENERATE) {
+		container = formatter_entry->get_container(sip_sorcery);
+		if (!container) {
+			ast_cli(a->fd, "CLI TRAVERSE failure.  No container found for object type %s\n", formatter_type);
+			return CLI_FAILURE ;
+		}
+	}
+
+	if (!is_container && cmd == CLI_GENERATE) {
+		return complete_show_sorcery_object(container, a->word, a->n);
+	}
+
+	if (is_container) {
+		if (!ao2_container_count(container)) {
+			dump_str_and_free(a->fd, context.output_buffer);
+			ast_cli(a->fd, "No objects found.\n\n");
+			return CLI_SUCCESS ;
+		}
+
+		if (!strcmp(formatter_type, "channel") || !strcmp(formatter_type, "contact")) {
+			s_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, NULL, NULL);
+		} else {
+			s_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, &ast_sorcery_object_id_compare, NULL);
+		}
+
+		ao2_container_dup(s_container, container, OBJ_ORDER_ASCENDING);
+
+		ao2_callback(s_container, OBJ_NODATA, formatter_entry->print_body, &context);
+	} else {
+		if (!(object = ast_sorcery_retrieve_by_id(
+			ast_sip_get_sorcery(), formatter_type, object_id))) {
+			dump_str_and_free(a->fd, context.output_buffer);
+			ast_cli(a->fd, "Unable to retrieve object %s\n", object_id);
+			return CLI_FAILURE ;
+		}
+		formatter_entry->print_body(object, &context, 0);
+	}
+
+	ast_str_append(&context.output_buffer, 0, "\n");
+	dump_str_and_free(a->fd, context.output_buffer);
+	return CLI_SUCCESS ;
+}
+
+static struct ast_cli_entry cli_commands[] = {
+	AST_CLI_DEFINE(cli_traverse_objects, "List PJSIP Channels", .command = "pjsip list channels",
+			.usage = "Usage: pjsip list channels\n       List the active PJSIP channels\n"),
+	AST_CLI_DEFINE(cli_traverse_objects, "Show PJSIP Channels", .command = "pjsip show channels",
+			.usage = "Usage: pjsip show channels\n       List(detailed) the active PJSIP channels\n"),
+
+	AST_CLI_DEFINE(cli_traverse_objects, "List PJSIP Aors", .command = "pjsip list aors",
+			.usage = "Usage: pjsip list aors\n       List the configured PJSIP Aors\n"),
+	AST_CLI_DEFINE(cli_traverse_objects, "Show PJSIP Aors", .command = "pjsip show aors",
+			.usage = "Usage: pjsip show aors\n       Show the configured PJSIP Aors\n"),
+	AST_CLI_DEFINE(cli_traverse_objects, "Show PJSIP Aor", .command = "pjsip show aor",
+			.usage = "Usage: pjsip show aor\n       Show the configured PJSIP Aor\n"),
+
+	AST_CLI_DEFINE(cli_traverse_objects, "List PJSIP Contacts", .command = "pjsip list contacts",
+			.usage = "Usage: pjsip list contacts\n       List the configured PJSIP contacts\n"),
+
+	AST_CLI_DEFINE(cli_traverse_objects, "List PJSIP Endpoints", .command = "pjsip list endpoints",
+			.usage = "Usage: pjsip list endpoints\n       List the configured PJSIP endpoints\n"),
+	AST_CLI_DEFINE(cli_traverse_objects, "Show PJSIP Endpoints", .command = "pjsip show endpoints",
+			.usage = "Usage: pjsip show endpoints\n       List(detailed) the configured PJSIP endpoints\n"),
+	AST_CLI_DEFINE(cli_traverse_objects, "Show PJSIP Endpoint", .command = "pjsip show endpoint",
+			.usage = "Usage: pjsip show endpoint <id>\n       Show the configured PJSIP endpoint\n"),
+
+	AST_CLI_DEFINE(cli_traverse_objects, "List PJSIP Auths", .command = "pjsip list auths",
+			.usage = "Usage: pjsip list auths\n       List the configured PJSIP Auths\n"),
+	AST_CLI_DEFINE(cli_traverse_objects, "Show PJSIP Auths", .command = "pjsip show auths",
+			.usage = "Usage: pjsip show auths\n       Show the configured PJSIP Auths\n"),
+	AST_CLI_DEFINE(cli_traverse_objects, "Show PJSIP Auth", .command = "pjsip show auth",
+			.usage = "Usage: pjsip show auth\n       Show the configured PJSIP Auth\n"),
+
+};
+
+
+static int compare_formatters(const void *a, const void *b) {
+	const struct ast_sip_cli_formatter_entry *afe = a;
+	const struct ast_sip_cli_formatter_entry *bfe = b;
+	if (!afe || !bfe) {
+		ast_log(LOG_ERROR, "One of the arguments to compare_formatters was NULL\n");
+		return -1;
+	}
+	return strcmp(afe->name, bfe->name);
+}
+
+static unsigned int hash_formatters(const void *a) {
+	const struct ast_sip_cli_formatter_entry *afe = a;
+	return ast_hashtab_hash_string(afe->name);
+}
+
+int ast_sip_register_cli_formatter(struct ast_sip_cli_formatter_entry *formatter) {
+	ast_hashtab_insert_safe(formatter_registry, formatter);
+	return 0;
+}
+
+int ast_sip_unregister_cli_formatter(struct ast_sip_cli_formatter_entry *formatter) {
+	struct ast_sip_cli_formatter_entry *entry = ast_hashtab_lookup(formatter_registry, formatter);
+	if (!entry) {
+		return -1;
+	}
+	ast_hashtab_remove_this_object(formatter_registry, entry);
+	return 0;
+}
+
+int ast_sip_initialize_cli(struct ast_sorcery *sorcery)
+{
+	formatter_registry = ast_hashtab_create(17, compare_formatters,
+		ast_hashtab_resize_java, ast_hashtab_newsize_java, hash_formatters, 0);
+	if (!formatter_registry) {
+		ast_log(LOG_ERROR, "Unable to create formatter_registry.\n");
+		return -1;
+	}
+
+	if (ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands))) {
+		ast_log(LOG_ERROR, "Failed to register pjsip cli commands.\n");
+		ast_hashtab_destroy(formatter_registry, ast_free);
+		return -1;
+	}
+	sip_sorcery = sorcery;
+	return 0;
+}
+
+void ast_sip_destroy_cli(void)
+{
+	ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
+	if (formatter_registry) {
+		ast_hashtab_destroy(formatter_registry, ast_free);
+	}
+}
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index 010eeb6970a4ea09ba145f61051e0bcb9ad0864f..c8e8e3bf13c966b79ef40ca4cbe5c2229c717f66 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -12,7 +12,8 @@
 
 #include "asterisk/res_pjsip.h"
 #include "include/res_pjsip_private.h"
-#include "asterisk/cli.h"
+#include "asterisk/res_pjsip_cli.h"
+#include "asterisk/acl.h"
 #include "asterisk/manager.h"
 #include "asterisk/astobj2.h"
 #include "asterisk/utils.h"
@@ -97,131 +98,6 @@ static const struct ast_sorcery_observer state_contact_observer = {
 	.deleted = persistent_endpoint_contact_observer,
 };
 
-static char *handle_cli_show_endpoints(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	RAII_VAR(struct ao2_container *, endpoints, NULL, ao2_cleanup);
-	struct ao2_iterator it_endpoints;
-	struct ast_sip_endpoint *endpoint;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "pjsip show endpoints";
-		e->usage =
-			"Usage: pjsip show endpoints\n"
-			"       Show the registered PJSIP endpoints\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	endpoints = ast_sip_get_endpoints();
-	if (!endpoints) {
-		return CLI_FAILURE;
-	}
-
-	if (!ao2_container_count(endpoints)) {
-		ast_cli(a->fd, "No endpoints found\n");
-		return CLI_SUCCESS;
-	}
-
-	ast_cli(a->fd, "Endpoints:\n");
-	it_endpoints = ao2_iterator_init(endpoints, 0);
-	while ((endpoint = ao2_iterator_next(&it_endpoints))) {
-		ast_cli(a->fd, "%s\n", ast_sorcery_object_get_id(endpoint));
-		ao2_ref(endpoint, -1);
-	}
-	ao2_iterator_destroy(&it_endpoints);
-	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 = "pjsip show endpoint";
-		e->usage =
-			"Usage: pjsip show endpoint <endpoint>\n"
-			"       Show the given PJSIP 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 PJSIP Endpoints"),
-	AST_CLI_DEFINE(cli_show_endpoint, "Show PJSIP Endpoint")
-};
 
 static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
@@ -956,7 +832,7 @@ static int sip_endpoint_apply_handler(const struct ast_sorcery *sorcery, void *o
 	return 0;
 }
 
-static const char *get_device_state(const struct ast_sip_endpoint *endpoint)
+const char *ast_sip_get_device_state(const struct ast_sip_endpoint *endpoint)
 {
 	char device[MAX_OBJECT_FIELD];
 
@@ -964,7 +840,7 @@ static const char *get_device_state(const struct ast_sip_endpoint *endpoint)
 	return ast_devstate2str(ast_device_state(device));
 }
 
-static struct ast_endpoint_snapshot *sip_get_endpoint_snapshot(
+struct ast_endpoint_snapshot *ast_sip_get_endpoint_snapshot(
 	const struct ast_sip_endpoint *endpoint)
 {
 	return ast_endpoint_latest_snapshot(
@@ -974,46 +850,44 @@ static struct ast_endpoint_snapshot *sip_get_endpoint_snapshot(
 
 int ast_sip_for_each_channel_snapshot(
 	const struct ast_endpoint_snapshot *endpoint_snapshot,
-	on_channel_snapshot_t on_channel_snapshot, void *arg)
+	ao2_callback_fn on_channel_snapshot, void *arg)
 {
 	int num, num_channels = endpoint_snapshot->num_channels;
-	RAII_VAR(struct stasis_cache *, cache, NULL, ao2_cleanup);
 
-	if (!on_channel_snapshot || !num_channels ||
-	    !(cache = ast_channel_cache())) {
+	if (!on_channel_snapshot || !num_channels) {
 		return 0;
 	}
 
-	ao2_ref(cache, +1);
-
 	for (num = 0; num < num_channels; ++num) {
-		RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
-		struct ast_channel_snapshot *snapshot;
-
-		msg = stasis_cache_get(cache, ast_channel_snapshot_type(),
-			endpoint_snapshot->channel_ids[num]);
+		RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
+		int res;
 
-		if (!(snapshot = stasis_message_data(msg))) {
+		snapshot = ast_channel_snapshot_get_latest(endpoint_snapshot->channel_ids[num]);
+		if (!snapshot) {
 			continue;
 		}
 
-		if (on_channel_snapshot(
-			    snapshot, num == (num_channels - 1), arg)) {
+		res = on_channel_snapshot(snapshot, arg, 0);
+		if (res) {
 			return -1;
 		}
 	}
 	return 0;
 }
 
-static int active_channels_to_str_cb(const struct ast_channel_snapshot *snapshot,
-				     int last, void *arg)
+int ast_sip_for_each_channel(
+	const struct ast_sip_endpoint *endpoint,
+	ao2_callback_fn on_channel_snapshot, void *arg)
+{
+	RAII_VAR(struct ast_endpoint_snapshot *, endpoint_snapshot, ast_sip_get_endpoint_snapshot(endpoint), ao2_cleanup);
+	return ast_sip_for_each_channel_snapshot(endpoint_snapshot, on_channel_snapshot, arg);
+}
+
+static int active_channels_to_str_cb(void *object, void *arg, int flags)
 {
+	const struct ast_channel_snapshot *snapshot = object;
 	struct ast_str **buf = arg;
-	if (last) {
-		ast_str_append(buf, 0, "%s", snapshot->name);
-	} else {
-		ast_str_append(buf, 0, "%s,", snapshot->name);
-	}
+	ast_str_append(buf, 0, "%s,", snapshot->name);
 	return 0;
 }
 
@@ -1022,7 +896,7 @@ static void active_channels_to_str(const struct ast_sip_endpoint *endpoint,
 {
 
 	RAII_VAR(struct ast_endpoint_snapshot *, endpoint_snapshot,
-		 sip_get_endpoint_snapshot(endpoint), ao2_cleanup);
+		 ast_sip_get_endpoint_snapshot(endpoint), ao2_cleanup);
 
 	if (endpoint_snapshot) {
 		return;
@@ -1030,6 +904,7 @@ static void active_channels_to_str(const struct ast_sip_endpoint *endpoint,
 
 	ast_sip_for_each_channel_snapshot(endpoint_snapshot,
 					  active_channels_to_str_cb, str);
+	ast_str_truncate(*str, -1);
 }
 
 #define AMI_DEFAULT_STR_SIZE 512
@@ -1078,7 +953,7 @@ int ast_sip_sorcery_object_to_ami(const void *obj, struct ast_str **buf)
 
 static int sip_endpoints_aors_ami(void *obj, void *arg, int flags)
 {
-	const struct ast_sip_aor *aor = obj;
+	struct ast_sip_aor *aor = obj;
 	struct ast_str **buf = arg;
 
 	ast_str_append(buf, 0, "Contacts: ");
@@ -1096,7 +971,7 @@ static int sip_endpoint_to_ami(const struct ast_sip_endpoint *endpoint,
 	}
 
 	ast_str_append(buf, 0, "DeviceState: %s\r\n",
-		       get_device_state(endpoint));
+		       ast_sip_get_device_state(endpoint));
 
 	ast_str_append(buf, 0, "ActiveChannels: ");
 	active_channels_to_str(endpoint, buf);
@@ -1206,7 +1081,7 @@ static int format_ami_endpoints(void *obj, void *arg, int flags)
 			     sip_endpoints_aors_ami, &buf);
 
 	ast_str_append(&buf, 0, "DeviceState: %s\r\n",
-		       get_device_state(endpoint));
+		       ast_sip_get_device_state(endpoint));
 
 	ast_str_append(&buf, 0, "ActiveChannels: ");
 	active_channels_to_str(endpoint, &buf);
@@ -1244,11 +1119,241 @@ static int ami_show_endpoints(struct mansession *s, const struct message *m)
 	return 0;
 }
 
-int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_module_info)
+static int populate_channel_container(void *obj, void *arg, int flags) {
+	struct ast_channel_snapshot *snapshot = obj;
+	struct ao2_container *container = arg;
+	ao2_link_flags(container, snapshot, OBJ_NOLOCK);
+	return 0;
+}
+
+static int gather_endpoint_channels(void *obj, void *arg, int flags) {
+	struct ast_sip_endpoint *endpoint = obj;
+	struct ao2_container *channels = arg;
+	ast_sip_for_each_channel(endpoint, populate_channel_container, channels);
+	return 0;
+}
+
+static struct ao2_container *cli_get_channel_container(struct ast_sorcery *sip_sorcery)
+{
+	RAII_VAR(struct ao2_container *, parent_container, NULL, ao2_cleanup);
+	RAII_VAR(struct ao2_container *, s_parent_container, NULL, ao2_cleanup);
+	struct ao2_container *child_container;
+
+	parent_container = ast_sorcery_retrieve_by_fields(sip_sorcery, "endpoint",
+				AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+	if (!parent_container) {
+		return NULL;
+	}
+
+	s_parent_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, &ast_sorcery_object_id_compare, NULL);
+	if (!s_parent_container) {
+		return NULL;
+	}
+
+	if (ao2_container_dup(s_parent_container, parent_container, OBJ_ORDER_ASCENDING)) {
+		return NULL;
+	}
+
+	child_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, NULL, NULL);
+	if (!child_container) {
+		return NULL;
+	}
+
+	ao2_callback(s_parent_container, OBJ_NODATA, gather_endpoint_channels, child_container);
+
+	return child_container;
+}
+
+static int cli_print_channel_header(void *obj, void *arg, int flags)
+{
+	struct ast_sip_cli_context *context = arg;
+	int indent = CLI_INDENT_TO_SPACES(context->indent_level);
+	int filler = CLI_LAST_TABSTOP - indent - 13;
+
+	if (!context->output_buffer) {
+		return -1;
+	}
+	ast_str_append(&context->output_buffer, 0,
+		"%*s:  <ChannelId%*.*s>  <State.....>  <Time(sec)>\n",
+		indent, "Channel", filler, filler, CLI_HEADER_FILLER);
+
+	context->indent_level++;
+	indent = CLI_INDENT_TO_SPACES(context->indent_level);
+	filler = CLI_LAST_TABSTOP - indent - 38;
+	ast_str_append(&context->output_buffer, 0,
+		"%*s:  <Codec>  Exten: <DialedExten%*.*s>  CLCID: <ConnectedLineCID.......>\n",
+		indent, "Codec", filler, filler, CLI_HEADER_FILLER);
+	context->indent_level--;
+	return 0;
+}
+
+static int cli_print_channel_body(void *obj, void *arg, int flags) {
+	struct ast_channel_snapshot *snapshot = obj;
+	struct ast_sip_cli_context *context = arg;
+	struct timeval current_time;
+	char *print_name = NULL;
+	int print_name_len;
+	int indent;
+	int flexwidth;
+
+	if (!context->output_buffer) {
+		return -1;
+	}
+
+	gettimeofday(&current_time, NULL);
+
+	print_name_len = strlen(snapshot->name) + strlen(snapshot->appl) + 2;
+	if (!(print_name = alloca(print_name_len))) {
+		return -1;
+	}
+
+	snprintf(print_name, print_name_len, "%s/%s", snapshot->name, snapshot->appl);
+
+	indent = CLI_INDENT_TO_SPACES(context->indent_level);
+	flexwidth = CLI_LAST_TABSTOP - indent;
+
+	ast_str_append(&context->output_buffer, 0, "%*s: %-*.*s %-12.12s  %11ld\n",
+		CLI_INDENT_TO_SPACES(context->indent_level), "Channel",
+		flexwidth, flexwidth,
+		print_name,
+		ast_state2str(snapshot->state),
+		current_time.tv_sec - snapshot->creationtime.tv_sec);
+
+	context->indent_level++;
+	indent = CLI_INDENT_TO_SPACES(context->indent_level);
+	flexwidth = CLI_LAST_TABSTOP - indent - 25;
+
+	ast_str_append(&context->output_buffer, 0, "%*s:  %-7s  Exten: %-*.*s  CLCID: \"%s\" <%s>\n",
+		indent, "Codec",
+		snapshot->nativeformats,
+		flexwidth, flexwidth,
+		snapshot->exten,
+		snapshot->connected_name,
+		snapshot->connected_number
+		);
+	context->indent_level--;
+	return 0;
+}
+
+static struct ao2_container *cli_get_endpoint_container(struct ast_sorcery *sip_sorcery)
+{
+	return ast_sip_get_endpoints();
+}
+
+static int cli_print_endpoint_header(void *obj, void *arg, int flags)
 {
-	if (ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands))) {
+	struct ast_sip_cli_context *context = arg;
+	struct ast_sip_cli_formatter_entry *formatter_entry;
+
+	if (!context->output_buffer) {
 		return -1;
 	}
+	ast_str_append(&context->output_buffer, 0,
+			" <Endpoint/CID................................................>  <State.....>  <Channels.>\n");
+
+	if (context->recurse) {
+		context->indent_level++;
+		formatter_entry = ast_sip_lookup_cli_formatter("auth");
+		if (formatter_entry) {
+			formatter_entry->print_header(NULL, context, 0);
+		}
+		formatter_entry = ast_sip_lookup_cli_formatter("aor");
+		if (formatter_entry) {
+			formatter_entry->print_header(NULL, context, 0);
+		}
+		formatter_entry = ast_sip_lookup_cli_formatter("identify");
+		if (formatter_entry) {
+			formatter_entry->print_header(NULL, context, 0);
+		}
+		formatter_entry = ast_sip_lookup_cli_formatter("channel");
+		if (formatter_entry) {
+			formatter_entry->print_header(NULL, context, 0);
+		}
+		context->indent_level--;
+	}
+	return 0;
+}
+
+static int cli_print_endpoint_body(void *obj, void *arg, int flags) {
+	struct ast_sip_endpoint *endpoint = obj;
+	RAII_VAR(struct ast_endpoint_snapshot *, endpoint_snapshot, ast_sip_get_endpoint_snapshot(endpoint), ao2_cleanup);
+	struct ast_sip_cli_context *context = arg;
+	const char *id = ast_sorcery_object_get_id(endpoint);
+	struct ast_sip_cli_formatter_entry *formatter_entry;
+	char *print_name = NULL;
+	int print_name_len;
+	char *number = S_COR(endpoint->id.self.number.valid,
+		endpoint->id.self.number.str, NULL);
+
+	if (!context->output_buffer) {
+		return -1;
+	}
+
+	context->current_endpoint = endpoint;
+
+	if (number) {
+		print_name_len = strlen(id) + strlen(number) + 2;
+		if (!(print_name = alloca(print_name_len))) {
+			return -1;
+		}
+		snprintf(print_name, print_name_len, "%s/%s", id, number);
+	}
+
+	ast_str_append(&context->output_buffer, 0, " %-62s  %-12.12s  %d of %.0f\n",
+		print_name ? print_name : id,
+		ast_sip_get_device_state(endpoint),
+		endpoint_snapshot->num_channels,
+		(double) endpoint->devicestate_busy_at ? endpoint->devicestate_busy_at :
+														INFINITY
+														);
+
+	if (context->recurse) {
+		context->indent_level++;
+
+		formatter_entry = ast_sip_lookup_cli_formatter("auth");
+		if (formatter_entry) {
+			context->auth_direction = "Out";
+			ast_sip_for_each_auth(&endpoint->outbound_auths, formatter_entry->print_body, context);
+			context->auth_direction = "In";
+			ast_sip_for_each_auth(&endpoint->inbound_auths, formatter_entry->print_body, context);
+		}
+		formatter_entry = ast_sip_lookup_cli_formatter("aor");
+		if (formatter_entry) {
+			ast_sip_for_each_aor(endpoint->aors, formatter_entry->print_body, context);
+		}
+		formatter_entry = ast_sip_lookup_cli_formatter("channel");
+		if (formatter_entry) {
+			ast_sip_for_each_channel(endpoint, formatter_entry->print_body, context);
+		}
+
+		context->indent_level--;
+	}
+
+	if (context->show_details || (context->show_details_only_level_0 && context->indent_level == 0)) {
+		ast_str_append(&context->output_buffer, 0, "\n");
+		ast_sip_cli_print_sorcery_objectset(endpoint, context, 0);
+	}
+
+	return 0;
+}
+
+static struct ast_sip_cli_formatter_entry cli_channel_formatter = {
+	.name = "channel",
+	.print_header = cli_print_channel_header,
+	.print_body = cli_print_channel_body,
+	.get_container = cli_get_channel_container,
+};
+
+static struct ast_sip_cli_formatter_entry cli_endpoint_formatter = {
+	.name = "endpoint",
+	.print_header = cli_print_endpoint_header,
+	.print_body = cli_print_endpoint_body,
+	.get_container = cli_get_endpoint_container,
+};
+
+
+int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_module_info)
+{
 
 	if (ast_manager_register_xml(AMI_SHOW_ENDPOINTS, EVENT_FLAG_SYSTEM, ami_show_endpoints) ||
 	    ast_manager_register_xml(AMI_SHOW_ENDPOINT, EVENT_FLAG_SYSTEM, ami_show_endpoint)) {
@@ -1266,6 +1371,11 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
 
 	ast_sorcery_apply_config(sip_sorcery, "res_pjsip");
 
+	ast_sip_initialize_cli(sip_sorcery);
+	ast_sip_register_cli_formatter(&cli_channel_formatter);
+	ast_sip_register_cli_formatter(&cli_endpoint_formatter);
+
+
 	if (ast_sip_initialize_sorcery_auth(sip_sorcery)) {
 		ast_log(LOG_ERROR, "Failed to register SIP authentication support\n");
 		ast_sorcery_unref(sip_sorcery);
@@ -1411,7 +1521,6 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
 
 void ast_res_pjsip_destroy_configuration(void)
 {
-	ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
 	ast_manager_unregister(AMI_SHOW_ENDPOINT);
 	ast_manager_unregister(AMI_SHOW_ENDPOINTS);
 	ast_sorcery_unref(sip_sorcery);
diff --git a/res/res_pjsip_endpoint_identifier_ip.c b/res/res_pjsip_endpoint_identifier_ip.c
index fe2948a1635b425497f8dd2fe3a647f0fa4eaeb3..34c8f2c393f6278ec2065eb884026371c840d4b1 100644
--- a/res/res_pjsip_endpoint_identifier_ip.c
+++ b/res/res_pjsip_endpoint_identifier_ip.c
@@ -255,6 +255,7 @@ static int reload_module(void)
 
 static int unload_module(void)
 {
+	ast_sip_unregister_endpoint_formatter(&endpoint_identify_formatter);
 	ast_sip_unregister_endpoint_identifier(&ip_identifier);
 	return 0;
 }
diff --git a/res/res_pjsip_registrar.c b/res/res_pjsip_registrar.c
index 9771bdc98ed41a00984cc1f8d777ee5b6588ad58..89e8cd141319e90dc4ca77976e8fa2d34f97a4fe 100644
--- a/res/res_pjsip_registrar.c
+++ b/res/res_pjsip_registrar.c
@@ -590,11 +590,9 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
 
 /* function pointer to callback needs to be within the module
    in order to avoid problems with an undefined symbol */
-static int sip_contact_to_str(const struct ast_sip_aor *aor,
-			      const struct ast_sip_contact *contact,
-			      int last, void *arg)
+static int sip_contact_to_str(void *acp, void *arg, int flags)
 {
-	return ast_sip_contact_to_str(aor, contact, last, arg);
+	return ast_sip_contact_to_str(acp, arg, flags);
 }
 
 static int ami_registrations_aor(void *obj, void *arg, int flags)