Index: res/res_features.c
===================================================================
RCS file: /usr/cvsroot/asterisk/res/res_features.c,v
retrieving revision 1.21
diff -u -r1.21 res_features.c
--- res/res_features.c	3 Nov 2004 22:37:55 -0000	1.21
+++ res/res_features.c	7 Dec 2004 22:12:05 -0000
@@ -20,6 +20,7 @@
 #include <asterisk/module.h>
 #include <asterisk/translate.h>
 #include <asterisk/say.h>
+#include <asterisk/app.h>
 #include <asterisk/channel_pvt.h>
 #include <asterisk/features.h>
 #include <asterisk/musiconhold.h>
@@ -40,7 +41,6 @@
 
 #define DEFAULT_PARK_TIME 45000
 #define DEFAULT_TRANSFER_DIGIT_TIMEOUT 3000
-
 static char *parkedcall = "ParkedCall";
 
 /* No more than 45 seconds parked before you do something with them */
@@ -93,6 +93,9 @@
 static struct ast_app *monitor_app=NULL;
 static int monitor_ok=1;
 
+static char global_xferstr[AST_MAX_EXTENSION] = "#";
+static char global_hangupstr[AST_MAX_EXTENSION] = "*";
+
 struct parkeduser {
 	struct ast_channel *chan;
 	struct timeval start;
@@ -286,16 +289,49 @@
 	return 0;
 }
 
-int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast_bridge_config *config)
+
+struct ast_bridge_thread_obj {
+	struct ast_channel *chan;
+	struct ast_channel *peer;
+};
+
+static void *ast_bridge_call_thread(void *data) {
+	struct ast_bridge_thread_obj *tobj = data;
+	struct ast_bridge_config bconfig;
+	memset(&bconfig,0,sizeof(struct ast_bridge_config));
+	ast_bridge_call(tobj->chan, tobj->peer,&bconfig);
+	ast_hangup(tobj->chan);
+	ast_hangup(tobj->peer);
+	tobj->chan = tobj->peer = NULL;
+	free(tobj);
+	tobj=NULL;
+	return NULL;
+}
+
+static void ast_bridge_call_thread_launch(void *data) {
+	pthread_t thread;
+	pthread_attr_t attr;
+	int result;
+	result = pthread_attr_init(&attr);
+	pthread_attr_setschedpolicy(&attr, SCHED_RR);
+	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+	result = ast_pthread_create(&thread, &attr,ast_bridge_call_thread, data);
+	result = pthread_attr_destroy(&attr);
+}
+
+int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer, struct ast_bridge_config *config)
 {
 	/* Copy voice back and forth between the two channels.  Give the peer
 	   the ability to transfer calls with '#<extension' syntax. */
 	int len;
 	struct ast_frame *f;
-	struct ast_channel *who;
+	struct ast_channel *who,*newchan, *xferchan=NULL;
+	struct ast_bridge_thread_obj *tobj;
 	char newext[256], *ptr;
 	int res;
-	int diff;
+	struct ast_bridge_config bconfig;
+	int diff,outstate=0;
+	char xferto[256],dialstr[256];
 	struct ast_option_header *aoh;
 	struct ast_channel *transferer;
 	struct ast_channel *transferee;
@@ -303,6 +339,28 @@
 	char *transferer_real_context;
 	int allowdisconnect_in,allowdisconnect_out,allowredirect_in,allowredirect_out;
 	char *monitor_exec;
+	char *collect = NULL;
+	char chan_collect[25];
+	char peer_collect[25];
+	char *xferstr=config->xferstr,*hangupstr=config->hangupstr;
+    int xferstr_len, hangupstr_len;
+	int ccount=0;
+	int max_collect=0;
+	char *cid_num;
+	char *cid_name;
+
+	if(ast_strlen_zero(xferstr))
+		xferstr = global_xferstr;
+	if(ast_strlen_zero(hangupstr))
+		hangupstr = global_hangupstr;
+
+    max_collect = ( xferstr_len = strlen(xferstr)) > (hangupstr_len = strlen(hangupstr)) ? xferstr_len : hangupstr_len;
+/* It seems unnecessary to check for length of zero before setting max collect. - Gryn */
+/*
+	if(! ast_strlen_zero(xferstr) || ! ast_strlen_zero(hangupstr)) {
+		max_collect = strlen(xferstr) > strlen(hangupstr) ? strlen(xferstr) : strlen(hangupstr);
+	}
+*/
 
 	if (chan && peer) {
 		pbx_builtin_setvar_helper(chan, "BRIDGEPEER", peer->name);
@@ -402,10 +460,51 @@
 					ast_channel_setoption(chan, ntohs(aoh->option), aoh->data, f->datalen - sizeof(struct ast_option_header), 0);
 			}
 		}
+ 		if (f && (f->frametype == AST_FRAME_DTMF)) {
+			collect = (who == chan) ? chan_collect : peer_collect;
+            if( ccount >= max_collect ) {
+                int x = 0;
+                while( x < ccount ) {
+                    collect[x] = collect[x+1];
+                    x++;
+                }
+                ccount = max_collect - 1;
+            }
+			collect[ccount++] = f->subclass;
+			collect[ccount] = '\0';
+		}
+
+        /* This code doesn't work either:  if xferstr was "##", then I push 9## on the phone,
+           the collect string would be equal to "9", "9#" and "#", but never "##".  So a tranfer would not occur.
+           The above block fixes this problem. - Gryn
+         */
+        /*
+		if (f && (f->frametype == AST_FRAME_DTMF)) {
+			collect = (who == chan) ? chan_collect : peer_collect;
+			collect[ccount++] = f->subclass;
+			collect[ccount] = '\0';
+			if(ccount >= max_collect)
+				ccount = 0;
+		}
+        */
+
+// Just some debugging lines...
+//        ast_verbose(VERBOSE_PREFIX_3 "allowredirect_in %d allowredirect_out %d", allowredirect_in, allowredirect_out );
+//        ast_verbose(VERBOSE_PREFIX_3 "max_collect %d xferstr %s chan collect: '%s' peer_collect: '%s'\n", max_collect, xferstr, chan_collect, peer_collect );
+
+
+        /* All three of these if's were broken in that they simply called strncmp(collect, target_string),
+           But if target_string was a different length than collect, then this comparison would not work.
+           e.g. If collect was "1234" and hangup happened on "34" and transfer on "####", then you would be
+           comparing "1234" with "34", when instead you want to always just compare the last two characters
+           in collect.
+           Fixed by adding "ccount >= targetstring_length" and adding a constant (ccount - targetstring_length)
+           to collect.  - Gryn */
+
 		/* check for '*', if we find it it's time to disconnect */
-		if (f && (f->frametype == AST_FRAME_DTMF) &&
+		if (f && (f->frametype == AST_FRAME_DTMF) && !ast_strlen_zero(hangupstr) &&
 			(((who == chan) && allowdisconnect_out) || ((who == peer) && allowdisconnect_in)) &&
-			(f->subclass == '*')) {
+			(ccount >= hangupstr_len && !strncmp(collect+(ccount - hangupstr_len),hangupstr,sizeof(collect)))) {
 			
 			if (option_verbose > 3)
 				ast_verbose(VERBOSE_PREFIX_3 "User hit %c to disconnect call.\n", f->subclass);
@@ -413,9 +512,164 @@
 			break;
 		}
 
-		if ((f->frametype == AST_FRAME_DTMF) &&
+		if ((f->frametype == AST_FRAME_DTMF) && !ast_strlen_zero(xferstr) &&
+			((allowredirect_in == AST_ATTENDED_TRANSFER && who == peer) || (allowredirect_out == AST_ATTENDED_TRANSFER && who == chan)) &&
+			(ccount >= xferstr_len && !strncmp(collect+(ccount - xferstr_len),xferstr,sizeof(collect)))) {
+			if(allowredirect_in &&  who == peer) {
+				transferer = peer;
+				transferee = chan;
+			}
+			else {
+				transferer = chan;
+				transferee = peer;
+			}
+			ast_autoservice_start(transferee);
+			ast_moh_start(transferee, NULL);
+			if((ast_dtget(transferer, xferto, sizeof(xferto), 10, 2))) {
+				if(!(transferer_real_context=pbx_builtin_getvar_helper(transferee, "TRANSFER_CONTEXT")) &&
+				   !(transferer_real_context=pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT"))) {
+					/* Use the non-macro context to transfer the call */
+					if(strlen(transferer->macrocontext))
+						transferer_real_context=transferer->macrocontext;
+					else
+						transferer_real_context=transferer->context;
+				}
+				
+				cid_num = transferer->cid.cid_num ? transferer->cid.cid_num: NULL;
+				cid_name = transferer->cid.cid_name ? transferer->cid.cid_name: NULL;
+				if (ast_exists_extension(transferer, transferer_real_context,xferto, 1, cid_num)) {
+					snprintf(dialstr, sizeof(dialstr), "%s@%s/n", xferto, transferer_real_context);
+					if((newchan = ast_request_and_dial("Local", ast_best_codec(transferer->nativeformats), dialstr,30000, &outstate, cid_num, cid_name))) {
+						res = ast_channel_make_compatible(transferer, newchan);
+						if (res < 0) {
+							ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", transferer->name, newchan->name);
+							ast_hangup(newchan);
+							break;
+						}
+						memset(&bconfig,0,sizeof(struct ast_bridge_config));
+						bconfig.allowdisconnect_in = bconfig.allowdisconnect_out = 1;
+						res = ast_bridge_call(transferer,newchan,&bconfig);
+						if(newchan->_softhangup || newchan->_state != AST_STATE_UP) {
+							ast_hangup(newchan);
+							if (f) {
+                                ast_frfree(f);
+								f = NULL;
+							}
+							if (!ast_streamfile(transferer, "beep", transferer->language)) {
+								if (ast_waitstream(transferer, "") < 0) {
+									ast_log(LOG_WARNING, "Failed to play courtesy tone!\n");
+								}
+							}
+							ast_moh_stop(transferee);
+							ast_autoservice_stop(transferee);
+							transferer->_softhangup = 0;
+							continue;
+						}
+						
+						res = ast_channel_make_compatible(transferee, newchan);
+						if (res < 0) {
+                            ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", transferee->name, newchan->name);
+                            ast_hangup(newchan);
+							break;
+                        }
+
+
+						ast_moh_stop(transferee);
+
+						if((ast_autoservice_stop(transferee) < 0)
+						   ||(ast_waitfordigit(transferee,100) < 0)
+						   || (ast_waitfordigit(newchan,100) < 0) 
+						   || ast_check_hangup(transferee) 
+						   || ast_check_hangup(newchan)) {
+							ast_hangup(newchan);
+							res = -1;
+							break;
+						}
+						
+
+						xferchan = ast_channel_alloc(0);
+						if (xferchan) {
+							/* Make formats okay */
+							xferchan->readformat = transferee->readformat;
+							xferchan->writeformat = transferee->writeformat;
+							ast_channel_masquerade(xferchan, transferee);
+							/* Setup the extensions and such */
+							snprintf(xferchan->name, sizeof (xferchan->name), "Transfered/%s",transferee->name);
+							strncpy(xferchan->context, transferee->context, sizeof(xferchan->context) - 1);
+							strncpy(xferchan->exten, transferee->exten, sizeof(xferchan->exten) - 1);
+							xferchan->priority = transferee->priority;
+							xferchan->_state = transferee->_state;
+							xferchan->zombie = xferchan->_softhangup = 0;
+							/* Make the masq execute */
+							f = ast_read(xferchan);
+							if (f) {
+								ast_frfree(f);
+								f = NULL;
+							}
+						} else {
+							ast_hangup(newchan);
+							break;
+						}
+						
+						tobj = malloc(sizeof(struct ast_bridge_thread_obj));
+						memset(tobj,0,sizeof(struct ast_bridge_thread_obj));
+						tobj->chan = xferchan;
+						tobj->peer = newchan;
+						ast_moh_stop(transferee);
+						ast_autoservice_stop(transferee);
+						if (!ast_streamfile(newchan, "beep", newchan->language)) {
+							if (ast_waitstream(newchan, "") < 0) {
+								ast_log(LOG_WARNING, "Failed to play courtesy tone!\n");
+							}
+						}
+						
+						
+						ast_bridge_call_thread_launch(tobj);
+						res = -1;
+						break;
+
+					} else {
+						ast_log(LOG_WARNING, "Unable to create channel Local/%s do you have chan_local?\n",dialstr);
+						ast_moh_stop(transferee);
+						ast_autoservice_stop(transferee);
+						res = ast_streamfile(transferer, "errbeep", transferer->language);
+						if (ast_waitstream(transferer, "") < 0) {
+							res = -1;
+							break;
+						}
+						continue;
+					}
+				} else {
+					ast_log(LOG_WARNING, "Extension Does Not Exist: %s\n",dialstr);
+					ast_moh_stop(transferee);
+					ast_autoservice_stop(transferee);
+					res = ast_streamfile(transferer, "errbeep", transferer->language);
+					if (ast_waitstream(transferer, "") < 0) {
+						res = -1;
+						break;
+					}
+				}
+			}  else {
+					ast_log(LOG_WARNING, "Did not read data.\n");
+				res = ast_streamfile(transferer, "errbeep", transferer->language);
+				if (ast_waitstream(transferer, "") < 0) {
+					res = -1;
+					break;
+				}
+			}
+			ast_moh_stop(transferee);
+			ast_autoservice_stop(transferee);
+		}
+        else if ((f->frametype == AST_FRAME_DTMF) && !ast_strlen_zero(xferstr) &&
+			((allowredirect_in == AST_REGULAR_TRANSFER && who == peer) || (allowredirect_out == AST_REGULAR_TRANSFER && who == chan)) &&
+			(ccount >= xferstr_len && !strncmp(collect + (ccount - xferstr_len),xferstr,sizeof(collect)))) {
+
+	/* This doesn't seem to work so above if statement replaces it... - Gryn */
+    /*
+		else if ((f->frametype == AST_FRAME_DTMF) &&
 			((allowredirect_in && who == peer) || (allowredirect_out && who == chan)) &&
 			(f->subclass == '#')) {
+    */
 				if (allowredirect_in &&  who == peer) {
 					transferer = peer;
 					transferee = chan;
@@ -535,18 +789,24 @@
 						ast_verbose(VERBOSE_PREFIX_2 "Hungup during autoservice stop on '%s'\n", transferee->name);
 				}
 			} else {
-            if (f && (f->frametype == AST_FRAME_DTMF)) {
-                  if (who == peer)
+				if (f && (f->frametype == AST_FRAME_DTMF) && ! config->mutedtmf) {
+					if (who == peer)
                         ast_write(chan, f);
-                  else
+					else
                         ast_write(peer, f);
-            }            
+				} 
 #if 1
 				ast_log(LOG_DEBUG, "Read from %s (%d,%d)\n", who->name, f->frametype, f->subclass);
 #endif
 			}
-         if (f)
-               ast_frfree(f);
+		if (f) {
+			ast_frfree(f);
+			f=NULL;
+		}
+	}
+	if (f) {
+		ast_frfree(f);
+		f=NULL;
 	}
 	return res;
 }
@@ -908,6 +1168,10 @@
 				strncpy(parking_ext, var->value, sizeof(parking_ext) - 1);
 			} else if (!strcasecmp(var->name, "context")) {
 				strncpy(parking_con, var->value, sizeof(parking_con) - 1);
+			} else if (!strcasecmp(var->name, "hangupstr")) {
+				strncpy(global_hangupstr, var->value, sizeof(global_hangupstr) - 1);
+			} else if (!strcasecmp(var->name, "xferstr")) {
+				strncpy(global_xferstr, var->value, sizeof(global_xferstr) - 1);
 			} else if (!strcasecmp(var->name, "parkingtime")) {
 				if ((sscanf(var->value, "%d", &parkingtime) != 1) || (parkingtime < 1)) {
 					ast_log(LOG_WARNING, "%s is not a valid parkingtime\n", var->value);
