--- app_voicemail.c.svn	2006-03-06 06:39:56.000000000 -0500
+++ app_voicemail.c	2006-03-06 06:41:36.000000000 -0500
@@ -51,6 +51,7 @@
 #include <sys/mman.h>
 #include <time.h>
 #include <dirent.h>
+#include <ctype.h> //needed for convert
 
 #include "asterisk.h"
 
@@ -266,9 +267,37 @@
 	int starting;
 	int repeats;
 };
+
+//to be used as a linked list of members in a voicemail group
+struct ast_vm_group_member {
+	char mailbox[80]; //this is the voice mailbox number for a user
+	char context[80]; //the context that this mailbox exists in
+	struct ast_vm_group_member *next; //what is the next member in the list?
+};
+
+/*
+** this is to store members into groups, so that we can forward 1 message to many
+** Edit voicemail.conf adding, note the name *must* be [groups]
+**  [groups]
+**  ;format
+**  ;GROUPNAME => GROUPID|CONTEXT,MEMBER1@context,MEMBER2@context,MEMBER3@context|GROUPANNOUNCEMSG
+**  ;example:
+**  Engineering => 1|default|4001@corporate,4002@corporate,4006@corporate,4010@someother|vm-engineeringgroup
+*/
+struct ast_vm_group {
+	char name[80]; //the group name (must be unique)
+	char id[80]; //the id number that is read off for groups (must be unique)
+	char context[AST_MAX_CONTEXT]; // the context to which this group belongs
+	char announce[80]; //the audio file to play to the forwarder
+	struct ast_vm_group_member *member; //this is our list of members
+	struct ast_vm_group *next; //this is the next group
+};
+
 static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msg,
 			    int option, signed char record_gain);
 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
+static char *ast_vm_convert(char *lastname, int total_digits);
+static struct ast_vm_user *ast_vm_directory_main(struct ast_channel *chan, char  *dir, char *context, int use_first_name);
 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
 			      char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
 			      signed char record_gain);
@@ -276,6 +305,8 @@
 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
 
 static void apply_options(struct ast_vm_user *vmu, const char *options);
+static int ast_vm_forwardgroup_main(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *curdir, int curmsg, char *fmt, signed char  record_gain);
+static struct ast_vm_group *ast_vm_find_group(const char *group_context, char *groupId);
 
 #ifdef USE_ODBC_STORAGE
 static char odbc_database[80];
@@ -389,6 +420,11 @@
 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
 struct vm_zone *zones = NULL;
 struct vm_zone *zonesl = NULL;
+
+// groups
+struct ast_vm_group *groups = NULL; // this is our global for the current groups
+struct ast_vm_group *groupsl = NULL; // this is our global for groups
+
 static int maxsilence;
 static int maxmsg;
 static int silencethreshold = 128;
@@ -1951,6 +1987,53 @@
 	free(z);
 }
 
+static void free_vm_group_member(struct ast_vm_group_member *member)
+{
+	if(member){
+		free(member);
+	}
+}
+
+/*
+** scan the list of global groups and try to find the group in question
+** returns NULL on failure
+** else returns address to the group
+*/
+static struct ast_vm_group *ast_vm_find_group(const char *group_context, char *groupId){
+	struct ast_vm_group *tmp_group;
+	if(groups){
+		tmp_group = groups;
+		while(tmp_group){
+			if(!strcmp(tmp_group->context,group_context) && !strncmp(tmp_group->id,groupId,sizeof(tmp_group->id))){
+				//we found it
+				return tmp_group;
+			}
+			tmp_group = tmp_group->next;
+		}
+	}
+	return NULL;
+}
+
+//clean up our group
+static void free_vm_group(struct ast_vm_group *group)
+{
+	struct ast_vm_group_member *tmp_member;
+	tmp_member = NULL;
+	if(group){
+		//do we have member undeneath us?
+		if(group->member){
+			while(group->member){
+				tmp_member = group->member;
+				ast_verbose( VERBOSE_PREFIX_3 "free_vm_group: freeing member '%s@%s'\n",tmp_member->mailbox,tmp_member->context);
+				group->member = group->member->next;
+				free_vm_group_member(tmp_member);
+			}
+		}
+		//time to free ourselves
+		free(group);
+	}
+}
+
 static char *mbox(int id)
 {
 	switch(id) {
@@ -2362,13 +2445,12 @@
 	signed char record_gain;
 };
 
-static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_options *options)
+static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_options *options, int *msgnum)
 {
 	char txtfile[256];
 	char callerid[256];
 	FILE *txt;
 	int res = 0;
-	int msgnum;
 	int duration = 0;
 	int ausemacro = 0;
 	int ousemacro = 0;
@@ -2529,7 +2611,7 @@
 	/* The meat of recording the message...  All the announcements and beeps have been played*/
 	ast_copy_string(fmt, vmfmts, sizeof(fmt));
 	if (!ast_strlen_zero(fmt)) {
-		msgnum = 0;
+		(*msgnum) = 0;
 
 		if (vm_lock_path(dir)) {
 			free_user(vmu);
@@ -2542,11 +2624,11 @@
 		 * message.  
 		 */
 		do {
-			make_file(fn, sizeof(fn), dir, msgnum);
+			make_file(fn, sizeof(fn), dir, (*msgnum));
 			if (!EXISTS(dir,msgnum,fn,chan->language))
 				break;
-			msgnum++;
-		} while (msgnum < vmu->maxmsg);
+			(*msgnum)++;
+		} while ((*msgnum) < vmu->maxmsg);
 
 		/* Now play the beep once we have the message number for our next message. */
 		if (res >= 0) {
@@ -2555,7 +2637,7 @@
 			if (!res)
 				res = ast_waitstream(chan, "");
 		}
-		if (msgnum < vmu->maxmsg) {
+		if ((*msgnum) < vmu->maxmsg) {
 			/* assign a variable with the name of the voicemail file */	  
 			pbx_builtin_setvar_helper(chan, "VM_MESSAGEFILE", fn);
 				
@@ -2606,7 +2688,7 @@
 			if (duration < vmminmessage) {
 				if (option_verbose > 2) 
 					ast_verbose( VERBOSE_PREFIX_3 "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, vmminmessage);
-				DELETE(dir,msgnum,fn);
+				DELETE(dir,*msgnum,fn);
 				/* XXX We should really give a prompt too short/option start again, with leave_vm_out called only after a timeout XXX */
 				pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
 				goto leave_vm_out;
@@ -2623,14 +2705,14 @@
 					context++;
 				}
 				if ((recip = find_user(&recipu, context, exten))) {
-					copy_message(chan, vmu, 0, msgnum, duration, recip, fmt);
+					copy_message(chan, vmu, 0, *msgnum, duration, recip, fmt);
 					free_user(recip);
 				}
 			}
 			if (ast_fileexists(fn, NULL, NULL)) {
-				STORE(dir, vmu->mailbox, vmu->context, msgnum);
-				notify_new_message(chan, vmu, msgnum, duration, fmt, chan->cid.cid_num, chan->cid.cid_name);
-				DISPOSE(dir, msgnum);
+				STORE(dir, vmu->mailbox, vmu->context, *msgnum);
+				notify_new_message(chan, vmu, *msgnum, duration, fmt, chan->cid.cid_num, chan->cid.cid_name);
+				DISPOSE(dir, *msgnum);
 			}
 			pbx_builtin_setvar_helper(chan, "VMSTATUS", "SUCCESS");
 		} else {
@@ -3267,6 +3349,7 @@
 			if (record_gain)
 				ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
 			cmd = ast_play_and_prepend(chan, NULL, file, 0, vmfmts, &duration, 1, silencethreshold, maxsilence);
+			cmd = 't';
 			if (record_gain)
 				ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
 			break;
@@ -3342,11 +3425,14 @@
 }
 
 static int forward_message(struct ast_channel *chan, char *context, char *dir, int curmsg, struct ast_vm_user *sender,
-			   char *fmt, int flag, signed char record_gain)
+			   char *fmt, int flag, signed char record_gain, 
+			struct vm_state *vms, struct ast_vm_user *vmtmp)
 {
 	char username[70]="";
 	char sys[256];
 	char todir[256];
+	char greeting[256];
+	char buff[80];
 	int todircount=0;
 	int duration;
 	struct ast_config *mif;
@@ -3355,14 +3441,20 @@
 	char callerid[512];
 	char ext_context[256]="";
 	int res = 0, cmd = 0;
-	struct ast_vm_user *receiver = NULL, *vmtmp;
-	AST_LIST_HEAD_NOLOCK_STATIC(extensions, ast_vm_user);
+	struct ast_vm_user *receiver = NULL, *extensions = NULL, *vmfree = NULL;
 	char tmp[256];
-	char *stringp, *s;
+	char *stringp=NULL, *s=NULL;
+	char group_option_key[2];
 	int saved_messages = 0, found = 0;
 	int valid_extensions = 0;
+	int silent = 0;
+	group_option_key[0] = '*';
+	group_option_key[1] = '\0';
+	if (vmtmp)
+		silent = 1;
 	
-	while (!res && !valid_extensions) {
+	// if we already have a user, don't bother prompting
+	while (!silent && (!res && !valid_extensions)) {
 		int use_directory = 0;
 		if(ast_test_flag((&globalflags), VM_DIRECFORWARD)) {
 			int done = 0;
@@ -3434,26 +3526,49 @@
 				ast_clear_flag((&globalflags), VM_DIRECFORWARD);	
 			}
 		} else 	{
-			/* Ask for an extension */
-			res = ast_streamfile(chan, "vm-extension", chan->language);	/* "extension" */
-			if (res)
-				break;
-			if ((res = ast_readstring(chan, username, sizeof(username) - 1, 2000, 10000, "#") < 0))
+			username[0] = '\0';
+			buff[0] = '\0';
+			res = ast_play_and_wait(chan, "vm-fwd-forwardinstr");
+			if (res > 0) { // did they push a key while we were in the prompt?
+				username[0] = res;
+				username[1] = '\0';
+			}
+
+			if ((res = ast_readstring(chan, buff, sizeof(buff) - 1, 2000, 10000, "#") < 0))
 				break;
+
+			strncat(username, buff, sizeof(username) - 1); //copy and remaining keys onto the username
+
 		}
 		
 		/* start all over if no username */
 		if (ast_strlen_zero(username))
 			continue;
 		stringp = username;
+
+		if (!strncmp(stringp, group_option_key, strlen(stringp))){
+			ast_verbose( VERBOSE_PREFIX_3 "User Pressed * for more forwarding group options\n");
+			// jump out to the group code
+			cmd = ast_vm_forwardgroup_main(chan,sender,vms,dir,curmsg,NULL,record_gain);
+			return 0;
+		}
 		s = strsep(&stringp, "*");
 		/* start optimistic */
 		valid_extensions = 1;
 		while (s) {
 			/* find_user is going to ast_malloc since we have a NULL as first argument */
+			// looping with this sounds like a potential memory leak w/o this check
+			if (receiver){
+				free_user(receiver);
+				receiver=NULL;
+			}
 			if ((receiver = find_user(NULL, context, s))) {
-				AST_LIST_INSERT_HEAD(&extensions, receiver, list);
+				//AST_LIST_INSERT_HEAD(&extensions, receiver, list);
+				//we found a match
+				vmtmp = extensions = receiver;
+				AST_LIST_NEXT(vmtmp, list) = NULL;
 				found++;
+				break; // jump out we found one
 			} else {
 				valid_extensions = 0;
 				break;
@@ -3461,28 +3576,60 @@
 			s = strsep(&stringp, "*");
 		}
 		/* break from the loop of reading the extensions */
-		if (valid_extensions)
-			break;
-		/* "I am sorry, that's not a valid extension.  Please try again." */
-		res = ast_play_and_wait(chan, "pbx-invalid");
+		if (valid_extensions) {
+			if (!silent){
+				// tell them who they are forwarding to and let them cancel if they want
+				snprintf(greeting, sizeof(greeting), "%s/voicemail/%s/%s/greet", (char *)ast_config_AST_SPOOL_DIR, receiver->context, receiver->mailbox);
+				if (ast_fileexists(greeting, NULL, chan->language) > 0) {
+					cmd = ast_play_and_wait(chan,greeting);
+				} else {
+					//they don't have a greeting, so let's play the digits
+					cmd = ast_say_digit_str(chan, receiver->mailbox, AST_DIGIT_ANY, chan->language);
+				}
+				if (!cmd) //allow them to cancel by hitting '*'
+					cmd = ast_play_and_wait_multi(chan,"vm-fwd-tocancel,vm-press,digits/star");
+				if (!cmd) //they have 3 seconds to cancel
+					cmd = ast_waitfordigit(chan, 3000);
+				switch(cmd){
+					case '*': //they want to cancel
+						valid_extensions = 0; //reset valid_extensions triggering another loop
+						break;
+					default:
+						break;
+				}
+			}
+		
+			//double check ourselves again
+			if (valid_extensions)
+				break;
+					
+		} else {
+			/* invalid extension, try again */
+			res = ast_play_and_wait(chan, "pbx-invalid");
+		}
 	}
 	/* check if we're clear to proceed */
-	if (AST_LIST_EMPTY(&extensions) || !valid_extensions)
+	if (!silent && (!extensions || !valid_extensions))
 		return res;
+	if (!vmtmp) // if not already set, set now
+		vmtmp = extensions;
 	if (flag==1) {
 		struct leave_vm_options leave_options;
+		int msg_num = 0;
 
 		/* Send VoiceMail */
 		memset(&leave_options, 0, sizeof(leave_options));
 		leave_options.record_gain = record_gain;
-		cmd = leave_voicemail(chan, username, &leave_options);
+		cmd = leave_voicemail(chan, username, &leave_options, &msg_num);
 	} else {
 
 		/* Forward VoiceMail */
 		RETRIEVE(dir, curmsg);
-		cmd = vm_forwardoptions(chan, sender, dir, curmsg, vmfmts, context, record_gain);
+		cmd = 0; // added by JS?
+		if (!silent) // if we aren't silent, give them more options
+			cmd = vm_forwardoptions(chan, sender, dir, curmsg, vmfmts, context, record_gain);
 		if (!cmd) {
-			AST_LIST_TRAVERSE_SAFE_BEGIN(&extensions, vmtmp, list) {
+			while (!res && vmtmp) {
 				/* if (ast_play_and_wait(chan, "vm-savedto"))
 					break;
 				*/
@@ -3554,13 +3701,13 @@
 				run_externnotify(vmtmp->context, vmtmp->mailbox);
 	
 				saved_messages++;
-				AST_LIST_REMOVE_CURRENT(&extensions, list);
-				free_user(vmtmp);
+				vmfree = vmtmp;
+				vmtmp = AST_LIST_NEXT(vmtmp, list);
+				free_user(vmfree);
 				if (res)
 					break;
 			}
-			AST_LIST_TRAVERSE_SAFE_END;
-			if (saved_messages > 0) {
+			if (!silent && (saved_messages > 0)) {
 				/* give confirmation that the message was saved */
 				/* commented out since we can't forward batches yet
 				if (saved_messages == 1)
@@ -5395,7 +5542,8 @@
 
 				case '5': /* Leave VoiceMail */
 					if (ast_test_flag(vmu, VM_SVMAIL)) {
-						cmd = forward_message(chan, context, vms.curdir, vms.curmsg, vmu, vmfmts, 1, record_gain);
+						cmd = ast_vm_forwardgroup_main(chan,vmu,&vms,vms.curdir,-1,NULL,record_gain);
+						//cmd = forward_message(chan, context, vms.curdir, vms.curmsg, vmu, vmfmts, 1, record_gain);
 						if (cmd == ERROR_LOCK_PATH) {
 							res = cmd;
 							goto out;
@@ -5424,7 +5572,8 @@
 						cmd = ast_play_and_wait(chan, "vm-tomakecall");
 					}
 					if (ast_test_flag(vmu, VM_SVMAIL) && !cmd)
-						cmd=ast_play_and_wait(chan, "vm-leavemsg");
+						cmd=ast_play_and_wait(chan, "vm-fwd-5tosendmessage");
+						//cmd=ast_play_and_wait(chan, "vm-leavemsg");
 					if (!cmd)
 						cmd = ast_play_and_wait(chan, "vm-starmain");
 					if (!cmd)
@@ -5476,7 +5625,7 @@
 	
 		case '8':
 			if (vms.lastmsg > -1) {
-				cmd = forward_message(chan, context, vms.curdir, vms.curmsg, vmu, vmfmts, 0, record_gain);
+				cmd = forward_message(chan, context, vms.curdir, vms.curmsg, vmu, vmfmts, 0, record_gain, &vms, NULL);
 				if (cmd == ERROR_LOCK_PATH) {
 					res = cmd;
 					goto out;
@@ -5600,6 +5749,7 @@
 	struct leave_vm_options leave_options;
 	struct ast_flags flags = { 0 };
 	char *opts[OPT_ARG_ARRAY_SIZE];
+	int msgnum = 0;
 	AST_DECLARE_APP_ARGS(args,
 		AST_APP_ARG(argv0);
 		AST_APP_ARG(argv1);
@@ -5661,7 +5811,7 @@
 		args.argv0 = ast_strdupa(tmp);
 	}
 
-	res = leave_voicemail(chan, args.argv0, &leave_options);
+	res = leave_voicemail(chan, args.argv0, &leave_options, &msgnum);
 
 	if (res == ERROR_LOCK_PATH) {
 		ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
@@ -5678,6 +5828,116 @@
 	return res;
 }
 
+//This code inserts a member into a voicemail group
+static int append_vm_group_member(struct ast_vm_group *group, char *mbox, char *context){
+	struct ast_vm_group_member *member;
+	struct ast_vm_group_member *tmp;
+	member = malloc(sizeof(struct ast_vm_group_member));
+	if(member){
+		//ok we need to copy the mbox and context into the member and insert that into the group
+		memset(member,0,sizeof(struct ast_vm_group_member));
+		strncpy(member->mailbox,mbox,sizeof(member->mailbox));
+		strncpy(member->context,context,sizeof(member->context));
+		member->next = NULL; //there is no one ahead of us
+
+		//does this group already have a member?
+		if(!group->member){
+			//nope so lets point this member into it
+			group->member = member;
+		} else {
+			//yes it does, so loop until we get to the last member then insert it there
+			tmp = group->member;
+			while(tmp){
+				//check for duplicate
+				if(!strncmp(tmp->mailbox,member->mailbox,strlen(tmp->mailbox)) &&
+					!strncmp(tmp->context,member->context,strlen(tmp->context))){
+					//this is a dupe
+					ast_log(LOG_WARNING, "Member already in group: voicemail member, '%s@%s'\n",mbox,context);
+					//free the member since we don't need it anymore
+					free_vm_group_member(member);
+					return 0;
+				}
+				if(!tmp->next){
+					//we're at the last one break out so we can set this 'next' to our new member
+					break;
+				}
+				tmp = tmp->next;
+			}
+			//ok we're at the end so insert
+			tmp->next = member;
+		}
+
+	} else {
+		ast_log(LOG_WARNING, "Failed to allocate voicemail member, '%s@%s'\n",mbox,context);
+		return -1; //failure
+	}
+	return 0;
+}
+
+/* This code will explode the users into the voicemail groups
+** Example:
+**  append_vm_group("groups","Engineering","4001@corporate,4002@corporate|vm-engineering");
+** Where "groups" is the [groups] section
+*/
+static int append_vm_group(char *section, char *groupName, char *data){
+	struct ast_vm_group *group;
+	char *users;
+	char *user;
+	char *mbox;
+	char *context;
+	char *id;
+	char *announce;
+	char *group_context;
+
+	//lets alloc a new voicemail group
+	group = malloc(sizeof(struct ast_vm_group));
+	if(group){
+		//erase this group
+		memset(group,0,sizeof(struct ast_vm_group));
+		strncpy(group->name,groupName,sizeof(group->name));
+		//if the group head pointer doesn't exist set it to point to this entry
+		if(!groups){
+			groups = group;
+		}
+		if(groupsl){
+			groupsl->next = group;
+		}
+		groupsl = group; //set the last pointer to this one
+
+		//set the group id
+		id = strsep(&data,"|");
+		strncpy(group->id,id,sizeof(group->id));
+
+		//set the group context
+		group_context = strsep(&data,"|");
+		strncpy(group->context,group_context,sizeof(group->context));
+
+		//allocate group members (we can change 'data', no one cares)
+		users = strsep(&data,"|"); //get all the users before options
+		while((user = strsep(&users,","))){
+			//loop until we get no more users
+			//now we will either have something like 1234@context
+			mbox = strsep(&user,"@");
+			context = user;
+			//time to append the user
+			append_vm_group_member(group, mbox, context); //we don't really care if they fail or succeed here
+		}
+		//do we have a group announce?
+		announce = strsep(&data,"|");
+		if(announce){
+			strncpy(group->announce,announce,sizeof(group->announce));
+		}
+
+		//time to handle options
+
+	} else {
+		//allocation failed, maybe we're out of memory?
+		ast_log(LOG_WARNING, "Failed to allocate voicemail group, '%s'\n",groupName);
+		return -1; //failure
+	}
+	return 0;
+}
+
 static int append_mailbox(char *context, char *mbox, char *data)
 {
 	/* Assumes lock is already held */
@@ -5805,6 +6065,10 @@
 "Usage: show voicemail zones\n"
 "       Lists zone message formats\n";
 
+static char show_voicemail_groups_help[] =
+"Usage: show voicemail groups [for <group context>]\n"
+"       Lists mailboxes@context in voicemail groups\n";
+
 static int handle_show_voicemail_users(int fd, int argc, char *argv[])
 {
 	struct ast_vm_user *vmu;
@@ -5880,6 +6144,39 @@
 	return RESULT_SUCCESS;
 }
 
+//do we need a lock here?
+static int handle_show_voicemail_groups(int fd, int argc, char *argv[])
+{
+	struct ast_vm_group *tmp_group;
+	struct ast_vm_group_member *tmp_member;
+
+	if ((argc < 3) || (argc > 5) || (argc == 4)) return RESULT_SHOWUSAGE;
+	else if ((argc == 5) && strcmp(argv[3],"for")) return RESULT_SHOWUSAGE;
+
+	tmp_group = groups;
+	if(!tmp_group){
+		ast_cli(fd, "There are no voicemail groups loaded\n");
+		return RESULT_FAILURE;
+	}
+
+	//since there are some, lets print them
+	while(tmp_group){
+		if ((argc == 3) || ((argc == 5) && !strcmp(argv[4],tmp_group->context))) {
+			//print the info about this group
+			ast_cli(fd, "%s %s [%s]:\n",tmp_group->name,tmp_group->context,tmp_group->id);
+			//do we have any members?
+			tmp_member = tmp_group->member;
+			while(tmp_member){
+				ast_cli(fd, "   %s@%s\n",tmp_member->mailbox,tmp_member->context);
+				tmp_member = tmp_member->next;
+			}
+			ast_cli(fd, "\n");
+		}
+		tmp_group = tmp_group->next;
+	}
+	return RESULT_SUCCESS;
+}
+
 static char *complete_show_voicemail_users(const char *line, const char *word, int pos, int state)
 {
 	int which = 0;
@@ -5920,9 +6217,15 @@
 	handle_show_voicemail_zones, "List zone message formats",
 	show_voicemail_zones_help, NULL };
 
+static struct ast_cli_entry show_voicemail_groups_cli =
+	{ { "show", "voicemail", "groups", NULL },
+	handle_show_voicemail_groups, "Lists mailboxes@context in voicemail groups",
+	show_voicemail_groups_help, NULL };
+
 static int load_config(void)
 {
 	struct ast_vm_user *cur;
+	struct ast_vm_group *curGroup, *curGroup1;
 	struct vm_zone *zcur, *zl;
 	struct ast_config *cfg;
 	char *cat;
@@ -5971,8 +6274,21 @@
 		zcur = zcur->next;
 		free_zone(zl);
 	}
+
+	// purge all the groups and members
+	curGroup = groups;
+	while(curGroup) {
+		curGroup1 = curGroup;
+		curGroup = curGroup->next;
+		free_vm_group(curGroup1);
+	}
 	zones = NULL;
 	zonesl = NULL;
+	groups = NULL;
+	groupsl = NULL;
+	curGroup = NULL;
+	curGroup1 = NULL;
+
 	memset(ext_pass_cmd, 0, sizeof(ext_pass_cmd));
 
 	if (cfg) {
@@ -6222,7 +6538,15 @@
 		while (cat) {
 			if (strcasecmp(cat, "general")) {
 				var = ast_variable_browse(cfg, cat);
-				if (strcasecmp(cat, "zonemessages")) {
+				if (!strcasecmp(cat, "groups")) {
+					/* Process the voicemail groups */
+					while(var){
+						ast_log(LOG_WARNING, "Found voice mail groups: %s | %s | %s\n", cat, var->name, var->value);
+						append_vm_group(cat, var->name, var->value);
+						var = var->next;
+					}
+				}
+				else if (strcasecmp(cat, "zonemessages")) {
 					/* Process mailboxes in this context */
 					while (var) {
 						append_mailbox(cat, var->name, var->value);
@@ -6388,6 +6712,7 @@
 	res |= ast_unregister_application(app4);
 	res |= ast_cli_unregister(&show_voicemail_users_cli);
 	res |= ast_cli_unregister(&show_voicemail_zones_cli);
+	res |= ast_cli_unregister(&show_voicemail_groups_cli);
 	ast_uninstall_vm_functions();
 	
 	STANDARD_HANGUP_LOCALUSERS;
@@ -6411,6 +6736,7 @@
 
 	ast_cli_register(&show_voicemail_users_cli);
 	ast_cli_register(&show_voicemail_zones_cli);
+	ast_cli_register(&show_voicemail_groups_cli);
 
 	/* compute the location of the voicemail spool directory */
 	snprintf(VM_SPOOL_DIR, sizeof(VM_SPOOL_DIR), "%s/voicemail/", ast_config_AST_SPOOL_DIR);
@@ -6489,10 +6815,576 @@
 	return 0;
 }
 
+
+/* This function converts a name->numbers, it has been taken from app_directory:convert
+**  There is a good reason why I just cut-and-pasted it with little modifications...
+*/
+static char *ast_vm_convert(char *lastname, int total_digits)
+{
+	char *tmp;
+	int lcount = 0;
+	tmp = malloc(total_digits + 1);
+	if (tmp) {
+		while((*lastname > 32) && lcount < total_digits) {
+			switch(toupper(*lastname)) {
+			case '1':
+				tmp[lcount++] = '1';
+				break;
+			case '2':
+			case 'A':
+			case 'B':
+			case 'C':
+				tmp[lcount++] = '2';
+				break;
+			case '3':
+			case 'D':
+			case 'E':
+			case 'F':
+				tmp[lcount++] = '3';
+				break;
+			case '4':
+			case 'G':
+			case 'H':
+			case 'I':
+				tmp[lcount++] = '4';
+				break;
+			case '5':
+			case 'J':
+			case 'K':
+			case 'L':
+				tmp[lcount++] = '5';
+				break;
+			case '6':
+			case 'M':
+			case 'N':
+			case 'O':
+				tmp[lcount++] = '6';
+				break;
+			case '7':
+			case 'P':
+			case 'Q':
+			case 'R':
+			case 'S':
+				tmp[lcount++] = '7';
+				break;
+			case '8':
+			case 'T':
+			case 'U':
+			case 'V':
+				tmp[lcount++] = '8';
+				break;
+			case '9':
+			case 'W':
+			case 'X':
+			case 'Y':
+			case 'Z':
+				tmp[lcount++] = '9';
+				break;
+			}
+			lastname++;
+		}
+		tmp[lcount] = '\0';
+	}
+	return tmp;
+}
+
+/*
+** Jesse Janzer, 03-02-04
+** This function returns a pointer to a voicemail user upon success
+**  success is determined if the user has 'chosen' a valid lastname-extension
+** This function was based heavily off of app_directory
+** NOTE: you can pass NULL into context if you don't want to match-by-context
+** Returns NULL on failure, pointer to the ast_vm_user on success
+*/
+static struct ast_vm_user *ast_vm_directory_main(struct ast_channel *chan, char *dir, char *context, int use_first_name){
+	struct ast_vm_user *tmp_user;
+	int cmd, res, cnt, match, wait_for_key, found_at_least_one;
+	char digits[4];
+	char greeting[256];
+	char *last_name, *pos, *buff;
+	cmd = 0;
+	res = 0;
+	match = 0;
+	cnt = 0;
+	wait_for_key = 0;
+	found_at_least_one = 0;
+	tmp_user=NULL;
+	last_name=NULL;
+	pos=NULL;
+	buff=NULL;
+
+	//we need to tell the user how to use this system
+	while(cmd == 0){
+		//much like directory, we're going to sit in a loop until they hit something
+		cmd = ast_play_and_wait(chan,"dir-intro");
+		//wait for three digits
+		if(cmd > 0){
+			digits[cnt++] = cmd;
+		} else {
+			//they didn't push a key, so let's sit tight for 5 seconds
+			cmd = ast_waitfordigit(chan, 5000);
+		}
+	}
+	while(cmd >= 0 && cnt < 3){ //let them bail on a hangup
+		cmd = ast_waitfordigit(chan, 5000);
+		if(cmd > 0){
+			digits[cnt++] = cmd;
+		} else{
+			//bail out
+			break;
+		}
+	}
+	digits[cnt] = '\0'; //terminate our string
+
+	//ok let's see if we can find this user
+
+	if(cnt > 2){ //only if we made at least 3 characters
+
+		//now we need to sit into a loop until they hangup, cancel, or select a user
+		//tmp_user = users; //point us to the first user
+		AST_LIST_LOCK(&users);
+		AST_LIST_TRAVERSE(&users, tmp_user, list) {
+			if (!(res >= 0 && !match))
+				break;
+ 
+			//do we grab the first word or last word?
+			if(use_first_name){
+				//first word
+				buff = ast_strdupa(tmp_user->fullname);
+
+				if(buff){
+					pos = strsep(&buff," ");
+				}
+
+			} else {
+				//last word
+				pos = strrchr(tmp_user->fullname, ' '); //jump to the last space
+				pos++;
+			}
+			if(pos){
+				last_name = ast_vm_convert(pos,3); //get a last name
+			}
+			if(last_name
+				&& !strncasecmp(last_name,digits,(sizeof(last_name) - 1))
+				&& (context ? !strncmp(context,tmp_user->context,sizeof(tmp_user->context) - 1) : 1)){
+
+				ast_verbose( VERBOSE_PREFIX_3 "match at last_name '%s' digits '%s'\n",last_name,digits);
+				//we found match (maybe), let's give them a prompt
+				found_at_least_one = 1;
+				snprintf(greeting, sizeof(greeting), "%s/voicemail/%s/%s/greet", (char *)ast_config_AST_SPOOL_DIR, context ? context : tmp_user->context, tmp_user->mailbox);
+				if (ast_fileexists(greeting, NULL, chan->language) > 0) {
+					res = ast_play_and_wait(chan,greeting);
+				} else {
+					//they don't have a greeting, so let's play the digits
+					res = ast_say_digit_str(chan, tmp_user->mailbox, AST_DIGIT_ANY, chan->language);
+				}
+
+				//loop until we get a response
+				wait_for_key = 0;
+				while(!wait_for_key && res >= 0){
+				//did they hit yes (1)?
+					switch(res){
+						case '1': //this is the one they want!
+							match = 1;
+							wait_for_key = 1;
+							break;
+						case '*': //they want to see the next available person
+							wait_for_key = 1;
+							break;
+						default: //ask them if they want this user
+							res = ast_play_and_wait(chan, "dir-instr");
+							if(!res) //timeout and repeat in 5 seconds
+								res = ast_waitfordigit(chan, 5000);
+							break;
+					}
+				}
+
+
+			}
+
+			if(last_name){ //purge last_name since convert mallocs
+				free(last_name);
+				last_name = NULL;
+			}
+			if(match){ //don't lose our place if we found the right person
+				break;
+			}
+		}
+		AST_LIST_UNLOCK(&users);
+
+	}
+
+	if(!match){
+		//there wasn't a match made
+		if(found_at_least_one) //play no more since we did find some but they didn't want them
+			res = ast_play_and_wait(chan, "dir-nomore");
+		else
+			res = ast_play_and_wait(chan, "dir-nomatch");
+	}
+	return tmp_user; //this will be NULL on failures due to the loop and prep
+}
+
+
+/* This is the main top level prompt for forwarding messages to group */
+static int ast_vm_forwardgroup_main(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *curdir, int curmsg, char *fmt, signed char  record_gain){
+	struct ast_vm_group_member *fwd_members_head, *fwd_member;
+	struct ast_vm_group *fwd_group, *pointer_group, *result_group;
+	struct ast_vm_user *fwd_user;
+	int cmd,more,res,fwd_at_least_one,cnt,is_forward;
+	char *ecodes;
+	char groupId[80];
+	char buff[256];
+	char tmp[80];
+	char groupgreeting[256];
+	struct leave_vm_options leave_options;
+
+	fwd_members_head = NULL;
+	fwd_member = NULL;
+	fwd_group = NULL;
+	fwd_user = NULL;
+	result_group = NULL;
+	pointer_group = NULL;
+	ecodes = NULL;
+	cmd = 0;
+	more = 1;
+	res = 0;
+	cnt = 0;
+	is_forward = 0;
+	fwd_at_least_one = 0; //just a simple int to tell us if we have at least one member
+
+	//are we forwarding a message?
+	if(curmsg >= 0){
+		//yes we are
+		is_forward = 1;
+	}
+
+	//init our group here
+	if(!fwd_group){
+		fwd_group = malloc(sizeof(struct ast_vm_group));
+		if(!fwd_group){
+			//uh oh we failed to malloc, so let's bail
+			ast_log(LOG_WARNING, "Failed to malloc fwd_group\n");
+			return -3;
+		}
+		//erase this group
+		memset(fwd_group,0,sizeof(struct ast_vm_group));
+	}
+
+	if(!chan){
+		//this is bad our channel doesn't exist, puke time
+		ast_log(LOG_WARNING, "chan doesn't exist, returning with failure\n");
+		return -1;
+	}
+	if(!vmu){
+		//this is bad our user doesn't exist
+		ast_log(LOG_WARNING, "vmu user doesn't exist, returning with failure\n");
+		return -2;
+	}
+
+	//"Please record you message, when you are finished press the # key"
+	if(curmsg == -1){
+		//we need to create a voicemail message into our own mailbox so we can easily forward to others
+		if(fmt == NULL){
+			fmt = "wav";
+		}
+		//they say extesion for arg[1], but I think they really mean mailbox
+		res = ast_play_and_wait(chan,"vm-fwd-recordmessage");
+
+		memset(&leave_options, 0, sizeof(leave_options));
+		leave_options.record_gain = record_gain;
+		ast_set_flag(&leave_options, OPT_SILENT);
+		res = leave_voicemail(chan, vmu->mailbox, &leave_options, &curmsg);
+	}
+
+
+	//here is the meat let's allow them to select which groups/members they want
+	while(more){
+		//do we have at least one member/group if so, give them the choice to send
+		if(fwd_at_least_one){
+			cmd = ast_play_and_wait_multi(chan,"vm-fwd-tosendthismessage,vm-press,digits/1");
+		}
+		// NOTE: I could string this all together with multi, but splitting it up makes it easier to read
+		if(!cmd) //To Add another group
+			cmd = ast_play_and_wait_multi(chan,"vm-fwd-toattachagroup,vm-press,digits/2");
+		if(!cmd) //To add another member
+			cmd = ast_play_and_wait_multi(chan,"vm-fwd-toattachauser,vm-press,digits/3");
+		if(is_forward){
+			if(!cmd) //For more forwarding options
+				cmd = ast_play_and_wait_multi(chan,"vm-fwd-fwdoptionspress,digits/4");
+		}
+		if(!cmd) //allow them to exit via * or hangup
+			cmd = ast_play_and_wait_multi(chan,"vm-fwd-tocancel,vm-press,digits/star");
+		if(!cmd) //wait for 6 seconds
+			cmd = ast_waitfordigit(chan, 6000);
+
+		switch(cmd){
+			//what key did the user press?
+			case '1': //they want to forward the message to all members in their list
+				cmd = 0;
+				//do we actually have any members?
+				if(!fwd_group || !fwd_group->member){
+					//no we don't so let's tell them and allow them to continue
+					if(!cmd)
+						cmd = ast_play_and_wait(chan,"vm-fwd-nomembers");
+					more = 1;
+					cmd = 0;
+				} else {
+					fwd_member = fwd_group->member;
+					while(fwd_member){
+						//send out a copy to each user
+						ast_verbose( VERBOSE_PREFIX_3 "forwarding message to %s@%s from %s\n", fwd_member->mailbox, fwd_member->context, vmu->mailbox);
+
+						fwd_user = NULL;
+
+						fwd_user = find_user(NULL,fwd_member->context, fwd_member->mailbox);
+						if(fwd_user){ //if this is a valid user, lets push
+							forward_message(chan, fwd_member->context, curdir, curmsg, vmu, vmfmts, 0, record_gain, vms, fwd_user);
+							//it looks scary to not free the user, but forward_message actually handles it for us
+							//free_user(fwd_user); // we don't need this anymore
+							fwd_user = NULL;
+						} else {
+							ast_log(LOG_WARNING, "Failed to find fwd_user\n");
+						}
+						fwd_member = fwd_member->next;
+					}
+					more = 0;
+
+					//now that it is sent, let's tell the user
+					if(!vms){
+						ast_log(LOG_WARNING, "vms wasn't defined, not giving any options to delete message\n");
+					} else if (curmsg < 0 || curmsg >= MAXMSG){
+						ast_log(LOG_WARNING, "message is out-of-bounds\n");
+					} else {
+						if(!cmd)
+							cmd = ast_play_and_wait(chan,"vm-fwd-yourmsgsent");
+						if(!cmd)
+							cmd = ast_play_and_wait_multi(chan,"vm-fwd-tosavemsg,vm-press,digits/1");
+						if(!cmd)
+							cmd = ast_play_and_wait_multi(chan,"vm-fwd-todiscardmsg,vm-press,digits/2");
+						if(!cmd) //wait for 6 seconds
+							cmd = ast_waitfordigit(chan, 6000);
+						switch(cmd){
+							case '2':
+								//this is the only case we really care about, let's remove this entry
+								vms->deleted[curmsg] = !vms->deleted[curmsg];
+								vms->heard[curmsg] = !vms->heard[curmsg];
+								if(vms->lastmsg < curmsg){ //in order for us to actually delete, we need to move lastmsg forward
+									vms->lastmsg = curmsg;
+								}
+								if (vms->deleted[curmsg])
+									cmd = ast_play_and_wait(chan, "vm-deleted");
+								else
+									cmd = ast_play_and_wait(chan, "vm-undeleted");
+								break;
+							default:
+								cmd = ast_play_and_wait(chan,"vm-msgsaved");
+								break;
+						}
+					}
+				}
+				break;
+			case '2': //they want to add more groups/members
+				//we need to loop through any groups that we don't already have
+				cmd = 0;
+				if(groups){ //our global list of groups, set via voicemail.conf
+					pointer_group = groups;
+					while(pointer_group){
+						ecodes = "#0123456789"; //if they hit any key, we want to pop out
+
+						//Did we already include this group?
+						if (!strcmp(pointer_group->context, vmu->context)) {
+							//ok we didn't so let's prompt for them
+							cmd = ast_play_and_wait(chan,"vm-fwd-forgroup");
+							ast_verbose( VERBOSE_PREFIX_3 "group announce '%s'\n",pointer_group->announce);
+							if(strlen(pointer_group->announce)){
+								snprintf(groupgreeting, sizeof(groupgreeting), "sounds/%s", pointer_group->announce);
+								if(ast_fileexists(groupgreeting,NULL,NULL)){
+									if(!cmd) //if we have a name prompt play it
+										cmd = ast_play_and_wait(chan,pointer_group->announce);
+								}
+							} else {
+								if(!cmd) //group id
+									cmd = ast_say_digit_str(chan, pointer_group->id, ecodes, chan->language);
+							}
+							if(!cmd) //press
+									cmd = ast_play_and_wait(chan,"vm-press");
+							if(!cmd) //unique id
+								cmd = ast_say_digit_str(chan, pointer_group->id, ecodes, chan->language);
+							/*if(!cmd) //followed by the pound key
+								play_and_wait(chan,"vm-followedbythepoundkey");*/
+						}
+						if(cmd){
+							break;
+						}
+
+
+						pointer_group = pointer_group->next; //position for the next one
+					}
+
+					if(ast_readstring(chan, groupId, sizeof(groupId) - 1, 3000, 3000, "#") < 0){
+						//failure to read a string
+						ast_log(LOG_WARNING, "failed to ast_readstring\n");
+					} else {
+						//if we received a key on while prompting for groups we need to cat that key and the received keys here
+						if(cmd > 0){
+							//a character was pressed during the prompts
+							buff[0] = cmd;
+							buff[1] = '\0';
+							strncat(buff,groupId,sizeof(buff));
+						} else {
+							strncpy(buff,groupId,sizeof(buff));
+						}
+
+						ast_verbose( VERBOSE_PREFIX_3 "groupId: '%s'\n",buff);
+
+						//lets see if this is a valid group
+						result_group = ast_vm_find_group(vmu->context, buff);
+						if(!result_group){
+							//NEED_PROMPT: "I'm sorry that is not a valid group"
+							cmd = ast_play_and_wait(chan,"vm-fwd-invalidgroup");
+							ast_log(LOG_WARNING, "user entered an invalid groupId: '%s'\n",groupId);
+							cmd = 0; //jump back to main forwarding thing
+							continue;
+						} else {
+							//ok that group was found, let's attach all of the members to our list
+							/*
+							** Make sure the list group exists
+							**  then loop through the resultant group
+							**   adding each member into the list group via append
+							*/
+
+
+
+							//loop through the result group
+							fwd_member = result_group->member;
+							while(fwd_member){
+								//we already get a saved copy, so don't forward this onto ourselves
+								if (!((!strcasecmp(vmu->context, fwd_member->context)) &&
+										(!strcasecmp(vmu->mailbox, fwd_member->mailbox)))){
+									ast_verbose( VERBOSE_PREFIX_3 "Attaching member: %s@%s to fwd_group\n",fwd_member->mailbox,fwd_member->context);
+									append_vm_group_member(fwd_group,fwd_member->mailbox,fwd_member->context);
+									fwd_at_least_one = 1;
+								} else {
+									ast_verbose( VERBOSE_PREFIX_3 "Not attaching member: %s@%s to fwd_group since sender is member\n",fwd_member->mailbox,fwd_member->context);
+								}
+								fwd_member = fwd_member->next;
+							}
+
+							//ok we're all done with this group
+
+
+						}
+
+
+					}
+
+				}
+				cmd = 0; //reset the command so that the user gets a new list
+				more = 1; //allow them to continue to insert
+				break;
+			case '3': //they want to add a user one at a time
+				/* give them 3 prompts
+				**  1) dial-by-name
+				**  2) dial-by-extension
+				**  *) cancel
+				*/
+				cmd = 0;
+				while(cmd == 0){ //while we're waiting, loop
+					cmd = ast_play_and_wait_multi(chan,"vm-fwd-toaddbylastname,vm-press,digits/1");
+					if(!cmd)
+						cmd = ast_play_and_wait_multi(chan,"vm-fwd-toaddbyextension,vm-press,digits/2");
+					if(!cmd)
+						cmd = ast_play_and_wait_multi(chan,"vm-fwd-tocancelstar");
+					if(!cmd) //wait for 6 seconds
+						cmd = ast_waitfordigit(chan, 6000);
+				}
+				switch(cmd){
+					//what did they push?
+					case '1':
+						fwd_user = ast_vm_directory_main(chan,curdir,NULL,0); //last name
+						if(fwd_user){ //they chose a user, so let's add it to the list
+							append_vm_group_member(fwd_group,fwd_user->mailbox,fwd_user->context);
+							fwd_at_least_one = 1;
+						}
+						break;
+					case '2':
+						//they want to just submit the extension
+						cmd = 0;
+						cnt = 0;
+						fwd_user = NULL;
+						while(cmd >= 0 && cnt < 2){ //bomb out if we get a good find or tries exceeds 2
+							cnt++;
+							buff[0] = '\0';
+							cmd = ast_play_and_wait_multi(chan,"vm-extension");
+							if(cmd > 0){ //did we already get a digit during extension?
+								buff[0] = cmd;
+								buff[1] = '\0';
+							}
+							cmd = ast_readstring(chan, tmp, sizeof(tmp) - 1, 2000, 10000, "#");
+							ast_log(LOG_WARNING, "I read '%s'\n",tmp);
+							if(cmd >= 0){
+								strncat(buff,tmp,sizeof(buff));
+							}
+							//can we find an extension now (only works from calling context)?
+							fwd_user = find_user(NULL,vmu->context,buff);
+							if(fwd_user){
+								append_vm_group_member(fwd_group,fwd_user->mailbox,fwd_user->context);
+								fwd_at_least_one = 1;
+								cnt = 2;
+							} else {
+								cmd = ast_play_and_wait_multi(chan,"invalid");
+								cmd = 0;
+							}
+						}
+						cmd = 2; //jump back to add user
+						break;
+					case '*':
+					default:
+						break;
+				}
+				if(cmd >= 0)
+					cmd = 0; //they are finished with their current selection
+
+				break;
+			case '4': //they want extra forwarding options
+				if(is_forward){ //only give them prepending options if they are forwarding
+					cmd = vm_forwardoptions(chan, vmu, curdir, curmsg, fmt, vmu->context, record_gain);
+				}
+				cmd = 0;
+				break;
+			case '5':
+			case '6':
+			case '7':
+			case '8':
+			case '9':
+			case '#':
+				cmd = ast_play_and_wait(chan, "vm-sorry");
+				cmd = 0;
+				break;
+			case 0: //just loop right back, nothing happened
+				break;
+			case '*': //they want to cancel their request
+			case -1: //hungup
+				more = 0;
+				break;
+			default:
+				//they did something we didn't expect
+				more = 0; //let's hop back
+				break;
+		}
+	}
+
+	//do we have a group still alloc'ed?
+	if(fwd_group)
+		free_vm_group(fwd_group);
+
+	return 0; //success let's exit
+}
+
 static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msg,
 			    int option, signed char record_gain)
 {
-	int res = 0;
+	int res = 0, msgnum = 0;
 	char filename[256],*origtime, *cid, *context, *name, *num;
 	struct ast_config *msg_cfg;
 	int retries = 0;
@@ -6626,7 +7518,7 @@
 					
 					memset(&leave_options, 0, sizeof(leave_options));
 					leave_options.record_gain = record_gain;
-					res = leave_voicemail(chan, num, &leave_options);
+					res = leave_voicemail(chan, num, &leave_options, &msgnum);
 					if (!res)
 						res = 't';
 					return res;
@@ -6640,6 +7532,9 @@
 			} 
 			res = 0;
 		}
+	} else if (option == 5) { /* Forward to group */
+		ast_verbose( VERBOSE_PREFIX_3 "User pressed 5, let's allow them to create a msg\n");
+		res = ast_vm_forwardgroup_main(chan,vmu,vms,vms->curdir,msg,NULL,record_gain); //we don't have a message/format
 	}
 
 	ast_config_destroy(msg_cfg);
