Index: app.c
===================================================================
RCS file: /usr/cvsroot/asterisk/app.c,v
retrieving revision 1.31
diff -u -r1.31 app.c
--- app.c	23 Sep 2004 16:58:09 -0000	1.31
+++ app.c	28 Sep 2004 01:17:38 -0000
@@ -25,6 +25,7 @@
 #include <asterisk/app.h>
 #include <asterisk/dsp.h>
 #include <asterisk/logger.h>
+#include <asterisk/indications.h>
 #include <asterisk/options.h>
 #include <asterisk/utils.h>
 #include <asterisk/lock.h>
@@ -33,6 +34,74 @@
 
 #define MAX_OTHER_FORMATS 10
 
+
+int ast_dtget(struct ast_channel *chan, char *collect, size_t size, int total, int to) {
+	struct tone_zone_sound *ts;
+	struct ast_frame *f;
+	time_t start,now;
+	int x=0;
+	int res=0;
+	int on=0;
+	int myto=0;
+
+	time(&start);
+	ast_set_write_format(chan, ast_best_codec(chan->nativeformats));
+    ast_set_read_format(chan, ast_best_codec(chan->nativeformats));
+	
+	ts = ast_get_indication_tone(chan->zone,"dial");
+    if (ts && ts->data[0]) {
+        res = ast_playtones_start(chan, 0, ts->data, 0);
+		on=1;
+	}
+	else 
+		ast_log(LOG_NOTICE,"Huh....? no dial?\n");
+
+	memset(collect,0,size);
+	for(x=0;x<total&&x<size;) {
+		time(&now);
+		myto = ast_strlen_zero(collect) ? 20 : to;
+		if((now - start) > myto) {
+			ast_playtones_stop(chan);
+			on=0;
+			break;
+		}
+		if(ast_waitfor(chan, 100) > -1) {
+			f = ast_read(chan);
+			if (f) {
+				if (f->frametype == AST_FRAME_DTMF) {
+					if(on) {
+						ast_playtones_stop(chan);
+						on=0;
+					}
+					if(f->subclass == '#') {
+						ast_playtones_stop(chan);
+                        on=0;
+						break;
+					}
+					else if(f->subclass == '*') {
+						memset(collect,0,size);
+						x=0;
+						res = ast_playtones_start(chan, 0, ts->data, 0);
+						on=1;
+					}
+					else {
+						collect[x++] = f->subclass;
+						time(&start);
+					}
+				}
+				ast_frfree(f);
+			}
+			else 
+				break;
+		}
+		else 
+			break;
+	}
+	
+	collect[x++] = '\0';
+	return x;
+}
+
 /* set timeout to 0 for "standard" timeouts. Set timeout to -1 for 
    "ludicrous time" (essentially never times out) */
 int ast_app_getdata(struct ast_channel *c, char *prompt, char *s, int maxlen, int timeout)
Index: apps/app_dial.c
===================================================================
RCS file: /usr/cvsroot/asterisk/apps/app_dial.c,v
retrieving revision 1.94
diff -u -r1.94 app_dial.c
--- apps/app_dial.c	22 Sep 2004 05:19:06 -0000	1.94
+++ apps/app_dial.c	28 Sep 2004 01:17:38 -0000
@@ -59,8 +59,11 @@
 "  This application returns -1 if the originating channel hangs up, or if the\n"
 "call is bridged and either of the parties in the bridge terminate the call.\n"
 "The option string may contain zero or more of the following characters:\n"
-"      't' -- allow the called user transfer the calling user by hitting #.\n"
+"      'd' -- Disable DTMF from being passed between the 2 channels.\n"
+"      't' -- allow the called user to transfer the calling user by hitting #.\n"
 "      'T' -- allow the calling user to transfer the call by hitting #.\n"
+"      'x' -- allow the called user to attended transfer the calling user by hitting #.\n"
+"      'X' -- allow the calling user to attended transfer the call by hitting #.\n"
 "      'f' -- Forces callerid to be set as the extension of the line \n"
 "             making/redirecting the outgoing call. For example, some PSTNs\n"
 "             don't allow callerids from other extensions then the ones\n"
@@ -77,6 +80,7 @@
 "      'S(x)' -- hangup the call after x seconds AFTER called party picked up\n"  	
 "      'D([digits])'  -- Send DTMF digit string *after* called party has answered\n"
 "             but before the bridge. (w=500ms sec pause)\n"
+"      'K(<transfer>:<hangup>)' -- Set Keybindings for transfer and hangup. e.g. K(555:123)\n"
 "      'L(x[:y][:z])' -- Limit the call to 'x' ms warning when 'y' ms are left\n"
 "             repeated every 'z' ms) Only 'x' is required, 'y' and 'z' are optional.\n"
 "             The following special variables are optional:\n"
@@ -449,13 +453,16 @@
 	char limitdata[256];
 	char *sdtmfptr;
 	char sdtmfdata[256] = "";
+	char *xferptr=NULL;
+	char *hangupptr=NULL;
+	char keybinddata[256] = "";
 	char *stack,*var;
 	char *mac = NULL, macroname[256] = "";
 	char status[256]="";
 	char toast[80];
 	int play_to_caller=0,play_to_callee=0;
 	int playargs=0, sentringing=0, moh=0;
-	int digit = 0;
+	int digit = 0,mutedtmf = 0;
 	time_t start_time, answer_time, end_time;
 
 	if (!data) {
@@ -523,6 +530,27 @@
 			else 
 				ast_log(LOG_WARNING, "D( Data lacking trailing ')'\n");
 		}
+
+		/* Key Binding */
+		if ((xferptr = strstr(transfer, "K("))) {
+			strncpy(keybinddata, xferptr + 2, sizeof(keybinddata) - 1);
+			/* Overwrite with X's what was the keybind info */
+			while (*xferptr && (*xferptr != ')')) 
+				*(xferptr++) = 'X';
+			if (*xferptr)
+				*xferptr = 'X';
+			/* Now find the end  */
+			xferptr = strchr(keybinddata, ')');
+			if (xferptr)
+				*xferptr = '\0';
+			else 
+				ast_log(LOG_WARNING, "K( Data lacking trailing ')'\n");
+			xferptr = keybinddata;
+			if((hangupptr = strchr(xferptr,':'))) {
+				*hangupptr = '\0';
+				hangupptr++;
+			}
+		}
 		
 		/* XXX LIMIT SUPPORT */
 		if ((limitptr = strstr(transfer, "L("))) {
@@ -704,20 +732,27 @@
 		memset(tmp, 0, sizeof(struct localuser));
 		if (transfer) {
 			if (strchr(transfer, 't'))
-				tmp->allowredirect_in = 1;
-                        else    tmp->allowredirect_in = 0;
+				tmp->allowredirect_in = AST_REGULAR_TRANSFER;
+			else if (strchr(transfer, 'x'))
+				tmp->allowredirect_in = AST_ATTENDED_TRANSFER;
+			else    tmp->allowredirect_in = 0;
 			if (strchr(transfer, 'T'))
-				tmp->allowredirect_out = 1;
-                        else    tmp->allowredirect_out = 0;
+				tmp->allowredirect_out = AST_REGULAR_TRANSFER;
+			else if (strchr(transfer, 'X'))
+				tmp->allowredirect_out = AST_ATTENDED_TRANSFER;
+			else    tmp->allowredirect_out = 0;
 			if (strchr(transfer, 'r'))
 				tmp->ringbackonly = 1;
-                        else    tmp->ringbackonly = 0;
+			else    tmp->ringbackonly = 0;
+			if (strchr(transfer, 'd'))
+				mutedtmf = 1;
+			else    mutedtmf = 0;
 			if (strchr(transfer, 'm'))
 				tmp->musiconhold = 1;
-                        else    tmp->musiconhold = 0;
+			else    tmp->musiconhold = 0;
 			if (strchr(transfer, 'H'))
 				allowdisconnect_out = tmp->allowdisconnect_out = 1;
-                        else    allowdisconnect_out = tmp->allowdisconnect_out = 0;
+			else    allowdisconnect_out = tmp->allowdisconnect_out = 0;
 			if(strchr(transfer, 'h'))
 				allowdisconnect_in = tmp->allowdisconnect_in = 1;
 			else	allowdisconnect_in = tmp->allowdisconnect_in = 0;
@@ -983,6 +1018,11 @@
 			config.warning_sound = warning_sound;
 			config.end_sound = end_sound;
 			config.start_sound = start_sound;
+			config.mutedtmf = mutedtmf;
+			if(xferptr) 
+				strncpy(config.xferstr,xferptr,sizeof(config.xferstr));
+			if(hangupptr)
+				strncpy(config.hangupstr,hangupptr,sizeof(config.hangupstr));
 			if (moh) {
 				moh = 0;
 				ast_moh_stop(chan);
Index: include/asterisk/app.h
===================================================================
RCS file: /usr/cvsroot/asterisk/include/asterisk/app.h,v
retrieving revision 1.15
diff -u -r1.15 app.h
--- include/asterisk/app.h	18 Sep 2004 14:01:35 -0000	1.15
+++ include/asterisk/app.h	28 Sep 2004 01:17:38 -0000
@@ -31,6 +31,10 @@
  *  is pressed during playback, it will immediately break out of the message and continue
  *  execution of your code.
  */
+
+/* present a dialtone and collect digits (DISA jr) */
+int ast_dtget(struct ast_channel *chan, char *collect, size_t size, int total, int to);
+
 extern int ast_app_getdata(struct ast_channel *c, char *prompt, char *s, int maxlen, int timeout);
 
 /* Full version with audiofd and controlfd.  NOTE: returns '2' on ctrlfd available, not '1' like other full functions */
Index: include/asterisk/channel.h
===================================================================
RCS file: /usr/cvsroot/asterisk/include/asterisk/channel.h,v
retrieving revision 1.55
diff -u -r1.55 channel.h
--- include/asterisk/channel.h	19 Sep 2004 16:17:18 -0000	1.55
+++ include/asterisk/channel.h	28 Sep 2004 01:17:38 -0000
@@ -273,6 +273,9 @@
 	int allowredirect_out;
 	int allowdisconnect_in;
 	int allowdisconnect_out;
+	char hangupstr[10];
+	char xferstr[10];
+	int mutedtmf;
 	long timelimit;
 	long play_warning;
 	long warning_freq;
Index: include/asterisk/features.h
===================================================================
RCS file: /usr/cvsroot/asterisk/include/asterisk/features.h,v
retrieving revision 1.3
diff -u -r1.3 features.h
--- include/asterisk/features.h	17 Jul 2004 23:56:12 -0000	1.3
+++ include/asterisk/features.h	28 Sep 2004 01:17:38 -0000
@@ -16,6 +16,8 @@
 
 #ifndef _AST_FEATURES_H
 #define _AST_FEATURES_H
+#define AST_REGULAR_TRANSFER 1
+#define AST_ATTENDED_TRANSFER 2
 
 //! Park a call and read back parked location
 /*! \param chan the channel to actually be parked
Index: res/res_features.c
===================================================================
RCS file: /usr/cvsroot/asterisk/res/res_features.c,v
retrieving revision 1.16
diff -u -r1.16 res_features.c
--- res/res_features.c	26 Sep 2004 20:47:01 -0000	1.16
+++ res/res_features.c	28 Sep 2004 01:20:36 -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;
@@ -284,16 +287,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;
@@ -301,6 +337,21 @@
 	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 ccount=0;
+	int max_collect=0;
+
+	if(ast_strlen_zero(xferstr))
+		xferstr = global_xferstr;
+	if(ast_strlen_zero(hangupstr))
+		hangupstr = global_hangupstr;
+
+	if(! ast_strlen_zero(xferstr) || ! ast_strlen_zero(hangupstr)) {
+		max_collect = strlen(xferstr) > strlen(hangupstr) ? strlen(xferstr) : strlen(hangupstr);
+	}
 
 	if (monitor_ok) {
 		if (!monitor_app) { 
@@ -386,10 +437,19 @@
 					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;
+			collect[ccount++] = f->subclass;
+			collect[ccount] = '\0';
+			if(ccount >= max_collect)
+				ccount = 0;
+		}
+
+
 		/* 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 == '*')) {
+			(!strncmp(collect,hangupstr,sizeof(collect)))) {
 			
 			if (option_verbose > 3)
 				ast_verbose(VERBOSE_PREFIX_3 "User hit %c to disconnect call.\n", f->subclass);
@@ -397,7 +457,152 @@
 			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)) &&
+			(!strncmp(collect,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;
+				}
+				if (ast_exists_extension(transferer, transferer_real_context,xferto, 1, transferer->callerid)) {
+					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, transferer->callerid))) {
+						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));
+						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) &&
 			((allowredirect_in && who == peer) || (allowredirect_out && who == chan)) &&
 			(f->subclass == '#')) {
 				if (allowredirect_in &&  who == peer) {
@@ -517,18 +722,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;
 }
@@ -888,6 +1099,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);
