Index: funcs/func_groupcount.c
===================================================================
--- funcs/func_groupcount.c	(revision 240226)
+++ funcs/func_groupcount.c	(working copy)
@@ -17,7 +17,7 @@
 /*! \file
  *
  * \brief Channel group related dialplan functions
- * 
+ *
  * \ingroup functions
  */
 
@@ -30,6 +30,7 @@
 #include "asterisk/pbx.h"
 #include "asterisk/utils.h"
 #include "asterisk/app.h"
+#include "asterisk/manager.h"
 
 /*** DOCUMENTATION
 	<function name="GROUP_COUNT" language="en_US">
@@ -89,7 +90,108 @@
 			<para>Gets a list of the groups set on a channel.</para>
 		</description>
 	</function>
+	<function name="GROUP_VAR" language="en_US">
+		<synopsis>
+			Gets or sets a channel group variable.
+		</synopsis>
+		<syntax>
+			<parameter name="group" argsep="@" required="true">
+				<argument name="group" required="true">
+					<para>Group name.</para>
+				</argument>
+				<argument name="category" required="false">
+					<para>Category name.</para>
+				</argument>
+			</parameter>
+			<parameter name="variable" required="true">
+				<para>Variable to get or set</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Gets or sets a channel group variable, if the group@category exists</para>
+		</description>
+	</function>
 
+	<manager name="GroupSet" language="en_US">
+		<synopsis>
+			Change channel group assignments.
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+			<parameter name="Group" required="false" />
+			<parameter name="Category" required="false" />
+		</syntax>
+		<description>
+			For more information, see the dialplan function GROUP().
+		</description>
+	</manager>
+	<manager name="GroupVarGet" language="en_US">
+		<synopsis>
+			Get channel group variables.
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+			<parameter name="Group" required="false" />
+			<parameter name="Category" required="false" />
+			<parameter name="Variable" required="true" />
+		</syntax>
+		<description>
+			At a minimum, either group or category must be provided.
+			For more information, see the dialplan function GROUP_VAR().
+		</description>
+	</manager>
+	<manager name="GroupVarSet" language="en_US">
+		<synopsis>
+			Set channel group variables.
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+			<parameter name="Group" required="false" />
+			<parameter name="Category" required="false" />
+			<parameter name="Variable" required="true" />
+			<parameter name="Value" required="false" />
+		</syntax>
+		<description>
+			At a minimum, either group or category must be provided.
+			For more information, see the dialplan function GROUP_VAR().
+		</description>
+	</manager>
+	<manager name="GroupsShow" language="en_US">
+		<synopsis>
+			Show channel groups.
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+		</syntax>
+		<description>
+			This will return a list of channel groups that are in use.
+			For more information, see the dialplan function GROUP().
+		</description>
+	</manager>
+	<manager name="GroupsShowChannels" language="en_US">
+		<synopsis>
+			Show group channel assignments.
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+		</syntax>
+		<description>
+			This will return a list the channels that are within each group.
+			For more information, see the dialplan function GROUP().
+		</description>
+	</manager>
+	<manager name="GroupsShowVariables" language="en_US">
+		<synopsis>
+			Show group channel variable assignments.
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+		</syntax>
+		<description>
+			This will return a list of groups and the variables assigned in each group.
+			For more information, see the dialplan function GROUP_VAR().
+		</description>
+	</manager>
  ***/
 
 static int group_count_function_read(struct ast_channel *chan, const char *cmd,
@@ -169,9 +271,9 @@
 {
 	int ret = -1;
 	struct ast_group_info *gi = NULL;
-	
+
 	ast_app_group_list_rdlock();
-	
+
 	for (gi = ast_app_group_list_head(); gi; gi = AST_LIST_NEXT(gi, group_list)) {
 		if (gi->chan != chan)
 			continue;
@@ -180,14 +282,14 @@
 		if (!ast_strlen_zero(gi->category) && !strcasecmp(gi->category, data))
 			break;
 	}
-	
+
 	if (gi) {
 		ast_copy_string(buf, gi->group, len);
 		ret = 0;
 	}
-	
+
 	ast_app_group_list_unlock();
-	
+
 	return ret;
 }
 
@@ -247,7 +349,7 @@
 				snprintf(tmp1, sizeof(tmp1), "%s", gi->group);
 		}
 	}
-	
+
 	ast_app_group_list_unlock();
 
 	ast_copy_string(buf, tmp1, len);
@@ -261,6 +363,340 @@
 	.write = NULL,
 };
 
+static int manager_group_set(struct mansession *s, const struct message *m)
+{
+	const char *channel = astman_get_header(m, "Channel");
+	const char *group = astman_get_header(m, "Group");
+	const char *category = astman_get_header(m, "Category");
+
+	char *group_category;
+	struct ast_channel *chan = NULL;
+
+	if (ast_strlen_zero(channel)) {
+		astman_send_error(s, m, "Channel not specified.");
+		return 0;
+	}
+
+        chan = ast_channel_get_by_name(channel);
+	if (!chan) {
+		astman_send_error(s, m, "Channel not found.");
+		return 0;
+	}
+
+	if (!group) {
+	       group = "";
+	}
+
+	if (!category) {
+	       category = "";
+	}
+
+	group_category = malloc(strlen(group) + strlen(category) + 2); /* 1 for @, 1 for NULL*/
+	sprintf(group_category, "%s@%s", group, category);
+
+	if (ast_app_group_set_channel(chan, group_category) == 0) {
+		astman_send_ack(s, m, "Group Set");
+		return 0;
+	}
+
+	astman_send_error(s, m, "Group set failed.");
+	return -1;
+}
+
+/* GROUP_VAR and related */
+
+static int group_var_function_read(struct ast_channel *chan, const char *cmd,
+			       char *data, char *buf, size_t len)
+{
+	char group[80] = "";
+	char category[80] = "";
+	const char *variable_value;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(groupcategory);
+		AST_APP_ARG(varname);
+	);
+	AST_STANDARD_APP_ARGS(args, data);
+
+	buf[0] = 0;
+
+	if (ast_strlen_zero(args.groupcategory) || ast_strlen_zero(args.varname)) {
+		ast_log(LOG_WARNING, "Syntax GROUP_VAR(group[@category],<varname>)\n");
+		return -1;
+	}
+
+	if (ast_app_group_split_group(args.groupcategory, group, sizeof(group), category, sizeof(category)))
+		return -1;
+
+	variable_value = ast_app_group_get_var(group, category, args.varname);
+
+	if (variable_value) {
+	  ast_copy_string(buf, variable_value, len);
+	}
+
+	return 0;
+}
+
+static int group_var_function_write(struct ast_channel *chan, const char *cmd,
+				char *data, const char *value)
+{
+	char group[80] = "";
+	char category[80] = "";
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(groupcategory);
+		AST_APP_ARG(varname);
+	);
+	AST_STANDARD_APP_ARGS(args, data);
+
+	if (ast_strlen_zero(args.groupcategory) || ast_strlen_zero(args.varname) || ast_strlen_zero(value)) {
+		ast_log(LOG_WARNING, "Syntax GROUP_VAR(group[@category],<varname>)=<value>)\n");
+		return -1;
+	}
+
+	ast_app_group_split_group(args.groupcategory, group, sizeof(group), category, sizeof(category));
+	ast_app_group_set_var(chan, group, category, args.varname, value);
+
+	return 0;
+}
+
+static struct ast_custom_function group_var_function = {
+	.name = "GROUP_VAR",
+	.read = group_var_function_read,
+	.write = group_var_function_write,
+};
+
+static int manager_group_var_get(struct mansession *s, const struct message *m)
+{
+	const char *id = astman_get_header(m, "ActionID");
+	const char *group = astman_get_header(m, "Group");
+	const char *category = astman_get_header(m, "Category");
+	const char *variable = astman_get_header(m, "Variable");
+	const char *variable_value;
+	char idText[256] = "";
+
+	if (ast_strlen_zero(group)) {
+		astman_send_error(s, m, "No group specified.");
+		return 0;
+	}
+
+	if (ast_strlen_zero(variable)) {
+		astman_send_error(s, m, "No variable specified.");
+		return 0;
+	}
+
+	if (!ast_strlen_zero(id))
+		snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
+
+	if (!category) {
+		category = "";
+	}
+
+	variable_value = ast_app_group_get_var(group, category, variable);
+
+	if (!variable_value) {
+		astman_send_error(s, m, "Group variable not found");
+		return 0;
+	}
+
+	astman_send_ack(s, m, "Result will follow");
+	astman_append(s, "Event: GroupVarGetResponse\r\n"
+			"Group: %s\r\n"
+			"Category: %s\r\n"
+			"Variable: %s\r\n"
+			"Value: %s\r\n"
+			"%s"
+			"\r\n",
+			group, category, variable, variable_value, idText);
+
+	return 0;
+}
+
+static int manager_group_var_set(struct mansession *s, const struct message *m)
+{
+	const char *group = astman_get_header(m, "Group");
+	const char *category = astman_get_header(m, "Category");
+	const char *variable = astman_get_header(m, "Variable");
+	const char *value = astman_get_header(m, "Value");
+	int result = 0;
+
+	if (ast_strlen_zero(group)) {
+		astman_send_error(s, m, "No group specified.");
+		return 0;
+	}
+
+	if (ast_strlen_zero(variable)) {
+		astman_send_error(s, m, "No variable specified.");
+		return 0;
+	}
+
+	if (!category) {
+		category = "";
+	}
+
+	if (!value) {
+		value = "";
+	}
+
+	result = ast_app_group_set_var(NULL, group, category, variable, value);
+
+	if (!result) {
+		astman_send_error(s, m, "Variable set failed (group doesn't exist)");
+		return 0;
+	}
+
+	astman_send_ack(s, m, "Variable Set");
+	return 0;
+}
+
+static int manager_groups_show(struct mansession *s, const struct message *m)
+{
+	const char *id = astman_get_header(m, "ActionID");
+	struct ast_group_meta *gmi = NULL;
+	char idText[256] = "";
+	int groups = 0;
+
+	if (!ast_strlen_zero(id)) {
+		snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
+	}
+
+	astman_send_listack(s, m, "Groups will follow", "start");
+
+	ast_app_group_meta_rdlock();
+	for (gmi = ast_app_group_meta_head(); gmi; gmi = AST_LIST_NEXT(gmi, group_meta_list)) {
+		astman_append(s,
+			"Event: GroupsShow\r\n"
+			"Group: %s\r\n"
+			"Category: %s\r\n"
+			"%s"
+			"\r\n", gmi->group, gmi->category, idText);
+
+		groups++;
+	}
+	ast_app_group_meta_unlock();
+
+	astman_append(s,
+		"Event: GroupsShowComplete\r\n"
+		"EventList: Complete\r\n"
+		"ListItems: %d\r\n"
+		"%s"
+		"\r\n", groups, idText);
+
+	return 0;
+}
+
+static int manager_groups_show_channels(struct mansession *s, const struct message *m)
+{
+	const char *id = astman_get_header(m, "ActionID"); 
+	struct ast_group_info *gi = NULL;
+	char idText[256] = "";
+	int channels = 0;
+
+	if (!ast_strlen_zero(id)) {
+		snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
+	}
+
+	astman_send_listack(s, m, "Group channels will follow", "start");
+
+	ast_app_group_list_rdlock();
+	for (gi = ast_app_group_list_head(); gi; gi = AST_LIST_NEXT(gi, group_list)) {
+		astman_append(s,
+			"Event: GroupsShowChannels\r\n"
+			"Group: %s\r\n"
+			"Category: %s\r\n"
+			"Channel: %s\r\n"
+			"%s"
+			"\r\n", gi->group, (gi->category ? gi->category : ""), gi->chan->name, idText);
+
+		channels++;
+	}
+	ast_app_group_list_unlock();
+
+	astman_append(s,
+		"Event: GroupsShowChannelsComplete\r\n"
+		"EventList: Complete\r\n"
+		"ListItems: %d\r\n"
+		"%s"
+		"\r\n", channels, idText);
+
+	return 0;
+}
+
+static int manager_groups_show_variables(struct mansession *s, const struct message *m)
+{
+	const char *id = astman_get_header(m, "ActionID");
+	struct ast_group_meta *gmi = NULL;
+	char idText[256] = "";
+	int groups = 0;
+
+	char *variables = NULL;
+	char *variables_pos = NULL;
+	struct varshead *headp;
+	struct ast_var_t *vardata;
+	int varstorage = 0;
+	int newvarstorage = 0;
+
+	if (!ast_strlen_zero(id)) {
+		snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
+	}
+
+	astman_send_listack(s, m, "Group variables will follow", "start");
+
+	ast_app_group_meta_rdlock();
+	for (gmi = ast_app_group_meta_head(); gmi; gmi = AST_LIST_NEXT(gmi, group_meta_list)) {
+		headp = &gmi->varshead;
+		newvarstorage = 1; /* For NULL */
+
+		AST_LIST_TRAVERSE(headp, vardata, entries) {
+			newvarstorage += strlen(ast_var_name(vardata)) + strlen(ast_var_value(vardata)) + 4; /* 2 for ": ", 2 for "\r\n" */
+		}
+
+		if (newvarstorage > varstorage) {
+			varstorage = newvarstorage;
+			if (variables) {
+			    free(variables);
+			}
+
+			variables = malloc(varstorage);
+		}
+
+		variables_pos = variables;
+
+		AST_LIST_TRAVERSE(headp, vardata, entries) {
+			strcpy(variables_pos, ast_var_name(vardata));  variables_pos += strlen(ast_var_name(vardata));
+			strcpy(variables_pos, ": ");		       variables_pos += 2;
+			strcpy(variables_pos, ast_var_value(vardata)); variables_pos += strlen(ast_var_value(vardata));
+			strcpy(variables_pos, "\r\n");		       variables_pos += 2;
+		}
+
+		*variables_pos = 0;
+
+		astman_append(s,
+			"Event: GroupsShowVariables\r\n"
+			"Group: %s\r\n"
+			"Category: %s\r\n"
+			"%s"
+			"VariablesStart: Variables will follow\r\n"
+			"%s"
+			"\r\n", gmi->group, (gmi->category ? gmi->category : ""), idText, variables);
+
+
+		groups++;
+	}
+	ast_app_group_meta_unlock();
+
+	if (variables) {
+		free(variables);
+	}
+
+	astman_append(s,
+		"Event: GroupsShowVariablesComplete\r\n"
+		"EventList: Complete\r\n"
+		"ListItems: %d\r\n"
+		"%s"
+		"\r\n", groups, idText);
+
+	return 0;
+}
+
 static int unload_module(void)
 {
 	int res = 0;
@@ -269,7 +705,14 @@
 	res |= ast_custom_function_unregister(&group_match_count_function);
 	res |= ast_custom_function_unregister(&group_list_function);
 	res |= ast_custom_function_unregister(&group_function);
+        res |= ast_custom_function_unregister(&group_var_function);
 
+	res |= ast_manager_unregister("GroupVarGet");
+	res |= ast_manager_unregister("GroupVarSet");
+	res |= ast_manager_unregister("GroupsShow");
+	res |= ast_manager_unregister("GroupsShowChannels");
+	res |= ast_manager_unregister("GroupsShowVariables");
+
 	return res;
 }
 
@@ -281,8 +724,20 @@
 	res |= ast_custom_function_register(&group_match_count_function);
 	res |= ast_custom_function_register(&group_list_function);
 	res |= ast_custom_function_register(&group_function);
+        res |= ast_custom_function_register(&group_var_function);
 
+	res |= ast_manager_register_xml("GroupSet",    EVENT_FLAG_CALL, manager_group_set);
+	res |= ast_manager_register_xml("GroupVarGet", EVENT_FLAG_CALL, manager_group_var_get);
+	res |= ast_manager_register_xml("GroupVarSet", EVENT_FLAG_CALL, manager_group_var_set);
+	res |= ast_manager_register_xml("GroupsShow",  EVENT_FLAG_REPORTING, manager_groups_show);
+	res |= ast_manager_register_xml("GroupsShowChannels",  EVENT_FLAG_REPORTING, manager_groups_show_channels);
+	res |= ast_manager_register_xml("GroupsShowVariables", EVENT_FLAG_REPORTING, manager_groups_show_variables);
+
 	return res;
 }
 
-AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Channel group dialplan functions");
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Channel group dialplan functions",
+		.load = load_module,
+		.unload = unload_module,
+	       );
Index: include/asterisk/channel.h
===================================================================
--- include/asterisk/channel.h	(revision 240226)
+++ include/asterisk/channel.h	(working copy)
@@ -2193,6 +2193,19 @@
 	AST_LIST_ENTRY(ast_group_info) group_list;
 };
 
+/*! \brief list of groups currently in use, with a pointer to a list of channels within the groups
+  */
+struct ast_group_meta {
+	int num_channels;				/*!< number of channels in this group */
+	struct varshead varshead;			/*!< A linked list for group variables. See \ref AstGroupVar */
+
+	AST_LIST_ENTRY(ast_group_info) channels_list;	/*!< List of channels in this group */
+	AST_LIST_ENTRY(ast_group_meta) group_meta_list;	/*!< Next group */
+
+	char *category;
+	char *group;
+};
+
 #define ast_channel_lock(chan) ao2_lock(chan)
 #define ast_channel_unlock(chan) ao2_unlock(chan)
 #define ast_channel_trylock(chan) ao2_trylock(chan)
Index: include/asterisk/app.h
===================================================================
--- include/asterisk/app.h	(revision 240226)
+++ include/asterisk/app.h	(working copy)
@@ -305,9 +305,21 @@
 /*! \brief Split a group string into group and category, returning a default category if none is provided. */
 int ast_app_group_split_group(const char *data, char *group, int group_max, char *category, int category_max);
 
+/*! \brief Remove a channel from a group meta assignment */
+int ast_app_group_remove_channel(struct ast_channel *chan, char *group, char *category);
+
+/*! \brief Add a channel to a group meta assignment, create a group meta item if it doesn't exist */
+int ast_app_group_add_channel(struct ast_channel *chan, char *group, char *category);
+
 /*! \brief Set the group for a channel, splitting the provided data into group and category, if specified. */
 int ast_app_group_set_channel(struct ast_channel *chan, const char *data);
 
+/*! \brief Set a group variable for a group@category */
+int ast_app_group_set_var(struct ast_channel *chan, const char *group, const char *category, const char *name, const char *value);
+
+/*! \brief Get a group variable from a group@category */
+const char* ast_app_group_get_var(const char *group, const char *category, const char *name);
+
 /*! \brief Get the current channel count of the specified group and category. */
 int ast_app_group_get_count(const char *group, const char *category);
 
@@ -332,6 +344,18 @@
 /*! \brief Unlock the group count list */
 int ast_app_group_list_unlock(void);
 
+/*! \brief Write Lock the group meta list */
+int ast_app_group_meta_wrlock(void);
+
+/*! \brief Read Lock the group meta list */
+int ast_app_group_meta_rdlock(void);
+
+/*! \brief Get the head of the group meta list */
+struct ast_group_meta *ast_app_group_meta_head(void);
+
+/*! \brief Unlock the group meta list */
+int ast_app_group_meta_unlock(void);
+
 /*!
   \brief Define an application argument
   \param name The name of the argument
Index: main/app.c
===================================================================
--- main/app.c	(revision 240226)
+++ main/app.c	(working copy)
@@ -49,13 +49,15 @@
 #include "asterisk/indications.h"
 #include "asterisk/linkedlists.h"
 #include "asterisk/threadstorage.h"
+#include "asterisk/manager.h"
 
 AST_THREADSTORAGE_PUBLIC(ast_str_thread_global_buf);
 
 
 #define AST_MAX_FORMATS 10
 
-static AST_RWLIST_HEAD_STATIC(groups, ast_group_info);
+static AST_RWLIST_HEAD_STATIC(groups, ast_group_info);      /*!< List of channels that are in groups */
+static AST_RWLIST_HEAD_STATIC(groups_meta, ast_group_meta); /*!< List of groups */
 
 /*!
  * \brief This function presents a dialtone and reads an extension into 'collect'
@@ -1048,6 +1050,7 @@
 	}
 	AST_RWLIST_TRAVERSE_SAFE_END;
 
+	/* Set new group and category */
 	if (ast_strlen_zero(group)) {
 		/* Enable unsetting the group */
 	} else if ((gi = calloc(1, len))) {
@@ -1059,6 +1062,8 @@
 			strcpy(gi->category, category);
 		}
 		AST_RWLIST_INSERT_TAIL(&groups, gi, group_list);
+
+		ast_app_group_add_channel(chan, group, category);
 	} else {
 		res = -1;
 	}
@@ -1068,6 +1073,131 @@
 	return res;
 }
 
+/*!
+ * \brief Set a group variable for a group@category
+ *
+ * \param chan      channel (if any) that is setting the group variable (can be NULL)
+ * \param group     group to set the variable on (cannot be null)
+ * \param category  category to set the variable on (cannot be null)
+ * \param name      name of the variable to set (cannot be null)
+ * \param value     value of the variable to set (cannot be null)
+ *
+ * \retval 1 On success
+ * \retval 0 On failure (group@category not found)
+ */
+int ast_app_group_set_var(struct ast_channel *chan, const char *group, const char *category, const char *name, const char *value) {
+	struct ast_group_meta *gmi = NULL;
+
+	struct varshead *headp;
+	struct ast_var_t *newvariable = NULL;
+
+	if (!group || !name) {
+		ast_log(LOG_WARNING, "GROUP assignment failed for %s@%s, group/name cannot be NULL, group variable '%s' not set\n", group, category, name);
+		return 0;
+        }
+
+	if (!category) {
+	category = "";
+	}
+
+	if (!value) {
+		value = "";
+	}
+
+	/* Find our group meta data */
+	AST_RWLIST_WRLOCK(&groups_meta);
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&groups_meta, gmi, group_meta_list) {
+		if ((strcasecmp(gmi->group, group) != 0) || (strcasecmp(gmi->category, category) != 0)) {
+			continue;
+		}
+
+		headp = &gmi->varshead;
+
+		AST_LIST_TRAVERSE(headp, newvariable, entries) {
+			if (strcasecmp(ast_var_name(newvariable), name) == 0) {
+				/* there is already such a variable, delete it */
+				AST_LIST_REMOVE(headp, newvariable, entries);
+				ast_var_delete(newvariable);
+				break;
+			}
+		}
+
+		newvariable = ast_var_assign(name, value);
+
+		AST_LIST_INSERT_HEAD(headp, newvariable, entries);
+		manager_event(EVENT_FLAG_DIALPLAN, "GroupVarSet",
+			"Channel: %s\r\n"
+			"Category: %s\r\n"
+			"Group: %s\r\n"
+			"Variable: %s\r\n"
+			"Value: %s\r\n"
+			"Uniqueid: %s\r\n",
+			chan ? chan->name : "none",
+			category, group, name, value,
+			chan ? chan->uniqueid : "none");
+
+	        break; /* We only have one list item per group@category */
+	}
+	AST_RWLIST_TRAVERSE_SAFE_END;
+	AST_RWLIST_UNLOCK(&groups_meta);
+
+	if (newvariable == NULL) {
+		ast_log(LOG_WARNING, "GROUP assignment %s@%s doesn't exist, group variable '%s' not set\n", group, category, name);
+		return 0;
+	}
+
+	return 1;
+}
+
+/*!
+ * \brief Get a group variable for a group@category
+ *
+ * \param group     group to get the variable from
+ * \param category  category to get the variable from
+ * \param name      name of the variable to get
+ *
+ * \retval NOT NULL On success return  char* of variable contents
+ * \retval NULL On failure (variable in group@category not found)
+ */
+const char* ast_app_group_get_var(const char *group, const char *category, const char *name) {
+	struct ast_group_meta *gmi = NULL;
+
+	struct varshead *headp;
+	const char *variable;
+	struct ast_var_t *ast_var;
+
+        if (!group || !name) {
+          return NULL;
+        }
+
+        if (!category) {
+          category = "";
+        }
+
+	/* Find our group meta data */
+	AST_RWLIST_RDLOCK(&groups_meta);
+	AST_RWLIST_TRAVERSE(&groups_meta, gmi, group_meta_list) {
+		if ((strcasecmp(gmi->group, group) != 0) || (strcasecmp(gmi->category, category) != 0)) {
+			continue;
+		}
+
+		headp = &gmi->varshead;
+
+		AST_LIST_TRAVERSE(headp, ast_var, entries) {
+			variable = ast_var_name(ast_var);
+
+			if (!strcasecmp(variable, name)) {
+				/* found it */
+				AST_RWLIST_UNLOCK(&groups_meta);
+				return ast_var_value(ast_var);
+			}
+		}
+	}
+	AST_RWLIST_UNLOCK(&groups_meta);
+
+	return NULL;
+}
+
 int ast_app_group_get_count(const char *group, const char *category)
 {
 	struct ast_group_info *gi = NULL;
@@ -1135,16 +1265,127 @@
 	return 0;
 }
 
+/* Remove a channel from a group meta assignment */
+/* Right now this just removes all the group metadata for a group@category if this is the last channel in the group@category */
+/* Ideally we would have direct pointers from the channel group assignments into the metadata struct, so we don't have to search */
+int ast_app_group_remove_channel(struct ast_channel *chan, char *group, char *category) {
+        struct ast_group_meta *gmi = NULL; /*!< Group metadatas   */
+
+	struct varshead *headp;
+	struct ast_var_t *vardata;
+
+        int destroy = 0;
+
+        if (!category) {
+          category = "";
+        }
+
+	AST_RWLIST_WRLOCK(&groups_meta);
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&groups_meta, gmi, group_meta_list) {
+		if (!strcasecmp(gmi->group, group) && !strcasecmp(gmi->category, category) && (--gmi->num_channels <= 0)) {
+			/* Remove all group variables */
+			headp = &gmi->varshead;
+
+			while ((vardata = AST_LIST_REMOVE_HEAD(headp, entries))) {
+				ast_var_delete(vardata);
+			}
+
+			AST_RWLIST_REMOVE_CURRENT(group_meta_list);
+			ast_free(gmi);
+
+                        destroy = 1;
+                        break; /* We only have one list item per group@category */
+		}
+	}
+	AST_RWLIST_TRAVERSE_SAFE_END;
+	AST_RWLIST_UNLOCK(&groups_meta);
+
+	manager_event(EVENT_FLAG_DIALPLAN, "GroupChannelRemove",
+		"Channel: %s\r\n"
+		"Category: %s\r\n"
+		"Group: %s\r\n"
+		"Uniqueid: %s\r\n",
+		chan->name,
+		category, group,
+		chan->uniqueid);
+
+        if (destroy) {
+		manager_event(EVENT_FLAG_DIALPLAN, "GroupDestroy",
+			"Category: %s\r\n"
+			"Group: %s\r\n",
+			category, group);
+        }
+
+        return 1;
+}
+
+/* Add a channel to a group meta assignment, create a group meta item if it doesn't exist */
+int ast_app_group_add_channel(struct ast_channel *chan, char *group, char *category) {
+	struct ast_group_meta *gmi = NULL; /*!< Group metadatas */
+
+	AST_RWLIST_WRLOCK(&groups_meta);
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&groups_meta, gmi, group_meta_list) {
+		if (!strcasecmp(gmi->group, group) && !strcasecmp(gmi->category, category)) {
+                        break; /* We only have one list item per group@category */
+		}
+	}
+	AST_RWLIST_TRAVERSE_SAFE_END;
+
+        if (!gmi) {
+		if (!(gmi = calloc(1, sizeof(struct ast_group_meta) + strlen(group) + 1 + strlen(category) + 1))) {
+		    AST_RWLIST_UNLOCK(&groups_meta);
+		    return -1;
+		}
+
+		gmi->group = (char *) gmi + sizeof(*gmi);
+		gmi->category = (char *) gmi + sizeof(*gmi) + strlen(group) + 1;
+
+		strcpy(gmi->group, group);
+
+		if (!ast_strlen_zero(category)) {
+			strcpy(gmi->category, category);
+		}
+
+		AST_RWLIST_INSERT_TAIL(&groups_meta, gmi, group_meta_list);
+
+		manager_event(EVENT_FLAG_DIALPLAN, "GroupCreate",
+			"Category: %s\r\n"
+			"Group: %s\r\n",
+			category, group);
+        }
+
+        gmi->num_channels++;
+	AST_RWLIST_UNLOCK(&groups_meta);
+
+	manager_event(EVENT_FLAG_DIALPLAN, "GroupChannelAdd",
+		"Channel: %s\r\n"
+		"Category: %s\r\n"
+		"Group: %s\r\n"
+		"Uniqueid: %s\r\n",
+		chan->name,
+		category, group,
+		chan->uniqueid);
+
+	return 1;
+}
+
 int ast_app_group_discard(struct ast_channel *chan)
 {
-	struct ast_group_info *gi = NULL;
+	struct ast_group_info *gi = NULL;  /*!< Group assignments */
 
+        /* Find and remove all groups associated to this channel */
 	AST_RWLIST_WRLOCK(&groups);
 	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&groups, gi, group_list) {
-		if (gi->chan == chan) {
-			AST_RWLIST_REMOVE_CURRENT(group_list);
-			ast_free(gi);
+		if (gi->chan != chan) {
+			continue;
 		}
+
+	        /* Find our group meta data, remove the entire group metadata if we're the last channel */
+		ast_app_group_remove_channel(chan, gi->group, gi->category);
+
+		/* Remove this group assignment for this channel */
+		AST_RWLIST_REMOVE_CURRENT(group_list);
+		ast_free(gi);
 	}
 	AST_RWLIST_TRAVERSE_SAFE_END;
 	AST_RWLIST_UNLOCK(&groups);
@@ -1172,6 +1413,26 @@
 	return AST_RWLIST_UNLOCK(&groups);
 }
 
+int ast_app_group_meta_wrlock(void)
+{
+	return AST_RWLIST_WRLOCK(&groups_meta);
+}
+
+int ast_app_group_meta_rdlock(void)
+{
+	return AST_RWLIST_RDLOCK(&groups_meta);
+}
+
+struct ast_group_meta *ast_app_group_meta_head(void)
+{
+	return AST_RWLIST_FIRST(&groups_meta);
+}
+
+int ast_app_group_meta_unlock(void)
+{
+	return AST_RWLIST_UNLOCK(&groups_meta);
+}
+
 #undef ast_app_separate_args
 unsigned int ast_app_separate_args(char *buf, char delim, char **array, int arraylen);
 
Index: main/cli.c
===================================================================
--- main/cli.c	(revision 240226)
+++ main/cli.c	(working copy)
@@ -1536,6 +1536,65 @@
 #undef FORMAT_STRING
 }
 
+static char *group_show_variables(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+#define FORMAT_STRING     "%-20s  %-20s\n"
+#define FORMAT_STRING_VAR "     %s=%s\n"
+
+	struct ast_group_meta *gmi = NULL;
+	struct varshead *headp;
+	struct ast_var_t *variable = NULL;
+	int numgroups = 0;
+	int havegroup = 0;
+	char group[80];
+	char category[80];
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "group show variables";
+		e->usage =
+			"Usage: group show variables [group@[category]]\n"
+			"       Lists all currently active groups and their variables.\n"
+			"       Optional group, or group@category can be used to only show a specific group\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc < 3 || a->argc > 4)
+		return CLI_SHOWUSAGE;
+
+	if (a->argc == 4) {
+		if (ast_app_group_split_group(a->argv[3], group, sizeof(group), category, sizeof(category))) {
+			return NULL;
+		}
+
+                havegroup = 1;
+	}
+
+	ast_cli(a->fd, "Group    Variables    Category\n");
+
+	/* Print group variables */
+	ast_app_group_meta_rdlock();
+	gmi = ast_app_group_meta_head();
+	while (gmi) {
+		ast_cli(a->fd, FORMAT_STRING, gmi->group, (strcmp(gmi->category, "") ? gmi->category : "(Default)"));
+		numgroups++;
+		headp = &gmi->varshead;
+
+		AST_LIST_TRAVERSE(headp, variable, entries) {
+			ast_cli(a->fd, FORMAT_STRING_VAR, ast_var_name(variable), ast_var_value(variable));
+		}
+
+		gmi = AST_LIST_NEXT(gmi, group_meta_list);
+	}
+	ast_app_group_meta_unlock();
+
+	ast_cli(a->fd, "%d active group%s\n", numgroups, ESS(numgroups));
+	return CLI_SUCCESS;
+#undef FORMAT_STRING
+}
+
 static char *handle_help(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
 
 static struct ast_cli_entry cli_cli[] = {
@@ -1558,6 +1617,8 @@
 
 	AST_CLI_DEFINE(group_show_channels, "Display active channels with group(s)"),
 
+	AST_CLI_DEFINE(group_show_variables, "Display active channels with group(s), along with group variables"),
+
 	AST_CLI_DEFINE(handle_help, "Display help list, or specific help on a command"),
 
 	AST_CLI_DEFINE(handle_logger_mute, "Toggle logging output to a console"),
