Index: channels/chan_skinny.c
===================================================================
--- channels/chan_skinny.c	(revision 69257)
+++ channels/chan_skinny.c	(working copy)
@@ -74,6 +74,7 @@
 #include "asterisk/abstract_jb.h"
 #include "asterisk/threadstorage.h"
 #include "asterisk/devicestate.h"
+#include "asterisk/indications.h"
 
 /*************************************
  * Skinny/Asterisk Protocol Settings *
@@ -615,6 +616,8 @@
 
 static const uint8_t soft_key_default_offhookwithfeat[] = {
 	SOFTKEY_REDIAL,
+	SOFTKEY_HOLD,
+	SOFTKEY_TRNSFER,
 	SOFTKEY_ENDCALL,
 };
 
@@ -938,8 +941,11 @@
 	int nat;
 	int outgoing;
 	int alreadygone;
+	int blindxfer;
+	int xferor;
 
 	struct skinny_subchannel *next;
+	struct skinny_subchannel *related;
 	struct skinny_line *parent;
 };
 
@@ -987,6 +993,7 @@
 
 	struct ast_codec_pref prefs;
 	struct skinny_subchannel *sub;
+	struct skinny_subchannel *activesub;
 	struct skinny_line *next;
 	struct skinny_device *parent;
 };
@@ -1086,6 +1093,7 @@
 };
 
 static int skinny_extensionstate_cb(char *context, char* exten, int state, void *data);
+static int skinny_transfer(struct skinny_subchannel *sub);
 
 static void *get_button_template(struct skinnysession *s, struct button_definition_template *btn)
 {
@@ -2409,7 +2417,7 @@
 
 	if (option_verbose > 2)
 		ast_verbose( VERBOSE_PREFIX_3 "Starting simple switch on '%s@%s'\n", l->name, d->name);
-
+	
 	len = strlen(d->exten);
 
 	while (len < AST_MAX_EXTENSION-1) {
@@ -2473,6 +2481,7 @@
 		} else if (!ast_canmatch_extension(c, c->context, d->exten, 1, c->cid.cid_num) &&
 			   ((d->exten[0] != '*') || (!ast_strlen_zero(d->exten) > 2))) {
 			ast_log(LOG_WARNING, "Can't match [%s] from '%s' in context %s\n", d->exten, c->cid.cid_num ? c->cid.cid_num : "<Unknown Caller>", c->context);
+			memset(d->exten, 0, sizeof(d->exten));
 			transmit_tone(s, SKINNY_REORDER);
 			/* hang out for 3 seconds to let congestion play */
 			ast_safe_sleep(c, 3000);
@@ -2587,6 +2596,7 @@
 		sub->rtp = NULL;
 	}
 	ast_mutex_unlock(&sub->lock);
+	l->activesub = NULL;
 	return 0;
 }
 
@@ -2598,6 +2608,15 @@
 	struct skinny_device *d = l->parent;
 	struct skinnysession *s = d->session;
 
+	if (sub->blindxfer) {
+		if (skinnydebug)
+			ast_verbose("skinny_answer(%s) on %s@%s-%d with BlindXFER, transferring\n",
+				ast->name, l->name, d->name, sub->callid);
+		ast_setstate(ast, AST_STATE_UP);
+		skinny_transfer(sub);
+		return 0;
+	}
+	
 	sub->cxmode = SKINNY_CX_SENDRECV;
 	if (!sub->rtp) {
 		start_rtp(sub);
@@ -2616,6 +2635,7 @@
 	transmit_callstate(s, l->instance, SKINNY_CONNECTED, sub->callid);
 	transmit_selectsoftkeys(s, l->instance, sub->callid, KEYDEF_CONNECTED);
 	transmit_displaypromptstatus(s, "Connected", 0, l->instance, sub->callid);
+	l->activesub = sub;
 	return res;
 }
 
@@ -2785,6 +2805,68 @@
 	}
 }
 
+static int skinny_transfer(struct skinny_subchannel *sub)
+{
+	struct skinny_subchannel *xferor;		/* the sub doing the transferring */
+	struct skinny_subchannel *xferee;		/* the sub being transferred */
+	const struct ind_tone_zone_sound *ts = NULL;
+		
+	if (ast_bridged_channel(sub->owner) || ast_bridged_channel(sub->related->owner)) {
+		if (sub->xferor) {
+			xferor = sub;
+			xferee = sub->related;
+		} else {
+			xferor = sub;
+			xferee = sub->related;
+		}
+		
+		if (skinnydebug) {
+			ast_verbose(VERBOSE_PREFIX_3 "Transferee channels (local/remote): %s and %s\n",
+				xferee->owner->name, ast_bridged_channel(xferee->owner)?ast_bridged_channel(xferee->owner)->name:"");
+			ast_verbose(VERBOSE_PREFIX_3 "Transferor channels (local/remote): %s and %s\n",
+				xferor->owner->name, ast_bridged_channel(xferor->owner)?ast_bridged_channel(xferor->owner)->name:"");
+		}
+		if (ast_bridged_channel(xferor->owner)) {
+			if (ast_bridged_channel(xferee->owner)) {
+				ast_queue_control(xferee->owner, AST_CONTROL_UNHOLD);
+			}
+			if (xferor->owner->_state == AST_STATE_RING) {
+				/* play ringing inband */
+				ts = ast_get_indication_tone(xferor->owner->zone, "ring");
+				ast_playtones_start(xferor->owner,0,ts->data, 1);
+			}
+			if (skinnydebug)
+				ast_verbose(VERBOSE_PREFIX_3 "Transfer Masquerading %s to %s\n",
+					xferee->owner->name, ast_bridged_channel(xferor->owner)?ast_bridged_channel(xferor->owner)->name:"");
+			if (ast_channel_masquerade(xferee->owner, ast_bridged_channel(xferor->owner))) {
+				ast_log(LOG_WARNING, "Unable to masquerade %s as %s\n",
+					ast_bridged_channel(xferor->owner)->name, xferee->owner->name);
+				return -1;
+			}
+		} else if (ast_bridged_channel(xferee->owner)) {
+			ast_queue_control(xferee->owner, AST_CONTROL_UNHOLD);
+			if (xferor->owner->_state == AST_STATE_RING) {
+				/* play ringing inband */
+				ts = ast_get_indication_tone(xferor->owner->zone, "ring");
+				ast_playtones_start(xferor->owner,0,ts->data, 1);
+			}
+			if (skinnydebug)
+				ast_verbose(VERBOSE_PREFIX_3 "Transfer Masquerading %s to %s\n",
+					xferor->owner->name, ast_bridged_channel(xferee->owner)?ast_bridged_channel(xferee->owner)->name:"");
+			if (ast_channel_masquerade(xferor->owner, ast_bridged_channel(xferee->owner))) {
+				ast_log(LOG_WARNING, "Unable to masquerade %s as %s\n",
+					ast_bridged_channel(xferee->owner)->name, xferor->owner->name);
+				return -1;
+			}
+			return 0;
+		} else {
+			if (option_debug)
+				ast_log(LOG_DEBUG, "Neither %s nor %s are in a bridge, nothing to transfer\n",
+					xferor->owner->name, xferee->owner->name);
+		}
+	}
+	return 0;
+}
 
 static int skinny_indicate(struct ast_channel *ast, int ind, const void *data, size_t datalen)
 {
@@ -2797,6 +2879,12 @@
 		ast_verbose(VERBOSE_PREFIX_3 "Asked to indicate '%s' condition on channel %s\n", control2str(ind), ast->name);
 	switch(ind) {
 	case AST_CONTROL_RINGING:
+		if (sub->blindxfer) {
+			if (skinnydebug)
+				ast_verbose(VERBOSE_PREFIX_3 "Channel %s set up for Blind Xfer, so Xfer rather than ring device\n", ast->name);
+			skinny_transfer(sub);
+			break;
+		}
 		if (ast->_state != AST_STATE_UP) {
 			if (!sub->progress) {
 				transmit_tone(s, SKINNY_ALERT);
@@ -2882,9 +2970,13 @@
 			sub->nat = l->nat;
 			sub->parent = l;
 			sub->onhold = 0;
+			sub->blindxfer = 0;
+			sub->xferor = 0;
+			sub->related = NULL;
 
 			sub->next = l->sub;
 			l->sub = sub;
+			l->activesub = sub;
 		}
 		tmp->tech = &skinny_tech;
 		tmp->tech_pvt = sub;
@@ -2950,7 +3042,7 @@
 
 	/* Channel needs to be put on hold */
 	if (skinnydebug)
-		ast_verbose("Putting on Hold(%d)\n", l->instance);
+		ast_verbose(VERBOSE_PREFIX_3 "Putting on Hold (%s)\n", sub->owner->name);
 
 	ast_queue_control_data(sub->owner, AST_CONTROL_HOLD,
 		S_OR(l->mohsuggest, NULL),
@@ -2977,6 +3069,7 @@
 	transmit_response(s, req);
 
 	transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_WINK);
+	transmit_callstate(s, l->instance, SKINNY_HOLD, sub->callid);
 	sub->onhold = 1;
 	return 1;
 }
@@ -2990,7 +3083,7 @@
 
 	/* Channel is on hold, so we will unhold */
 	if (skinnydebug)
-		ast_verbose("Taking off Hold(%d)\n", l->instance);
+		ast_verbose(VERBOSE_PREFIX_3 "Taking off Hold (%s)\n", sub->owner->name);
 
 	ast_queue_control(sub->owner, AST_CONTROL_UNHOLD);
 
@@ -3002,10 +3095,93 @@
 
 	transmit_connect(s, sub);
 	transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON);
+	transmit_callstate(s, l->instance, SKINNY_CONNECTED, sub->callid);
 	sub->onhold = 0;
 	return 1;
 }
 
+static int handle_hold_button(struct skinny_subchannel *sub)
+{
+	if (!sub)
+		return -1;
+	if (sub->related) {
+		skinny_hold(sub);
+		skinny_unhold(sub->related);
+		sub->parent->activesub = sub->related;
+	} else {
+		if (sub->onhold) {
+			skinny_unhold(sub);
+			transmit_selectsoftkeys(sub->parent->parent->session, sub->parent->instance, sub->callid, KEYDEF_CONNECTED);
+		} else {
+			skinny_hold(sub);
+			transmit_selectsoftkeys(sub->parent->parent->session, sub->parent->instance, sub->callid, KEYDEF_ONHOLD);
+		}
+	}
+	return 1;
+}
+
+static int handle_transfer_button(struct skinny_subchannel *sub)
+{
+	struct skinny_line *l = sub->parent;
+	struct skinny_device *d = l->parent;
+	struct skinnysession *s = d->session;
+	struct skinny_subchannel *newsub;
+	struct ast_channel *c;
+	pthread_t t;
+
+	if (!sub) {
+		ast_verbose("Transfer: No subchannel to transfer\n");
+		return -1;
+	}
+	if (!sub->related) {
+		/* Another sub has not been created so this must be first XFER press */
+		if (!sub->onhold) {
+			skinny_hold(sub);
+		}
+		c = skinny_new(l, AST_STATE_DOWN);
+		if (c) {
+			newsub = c->tech_pvt;
+			/* point the sub and newsub at each other so we know they are related */
+			newsub->related = sub;
+			sub->related = newsub;
+			newsub->xferor = 1;
+			l->activesub = newsub;
+			transmit_callstate(s, l->instance, SKINNY_OFFHOOK, newsub->callid);
+			if (skinnydebug)
+				ast_verbose("Attempting to Clear display on Skinny %s@%s\n", l->name, d->name);
+			transmit_displaymessage(s, NULL); /* clear display */
+			transmit_tone(s, SKINNY_DIALTONE);
+			transmit_selectsoftkeys(s, l->instance, newsub->callid, KEYDEF_OFFHOOKWITHFEAT);
+			/* start the switch thread */
+			if (ast_pthread_create(&t, NULL, skinny_ss, c)) {
+				ast_log(LOG_WARNING, "Unable to create switch thread: %s\n", strerror(errno));
+				ast_hangup(c);
+			}
+		} else {
+			ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
+		}
+	} else {
+		/* We already have a related sub so we can either complete XFER or go into BLINDXFER (or cancel BLINDXFER */
+		if (sub->blindxfer) {
+			/* toggle blindxfer off */
+			sub->blindxfer = 0;
+			sub->related->blindxfer = 0;
+			/* we really need some indications */
+		} else {
+			/* We were doing attended transfer */
+			if (sub->owner->_state == AST_STATE_DOWN || sub->related->owner->_state == AST_STATE_DOWN) {
+				/* one of the subs so we cant transfer yet, toggle blindxfer on */
+				sub->blindxfer = 1;
+				sub->related->blindxfer = 1;
+			} else {
+				/* big assumption we have two channels, lets transfer */
+				skinny_transfer(sub);
+			}
+		}
+	}
+	return 0;
+}
+
 static int handle_keep_alive_message(struct skinny_req *req, struct skinnysession *s)
 {
 	if (!(req = req_alloc(0, KEEP_ALIVE_ACK_MESSAGE)))
@@ -3110,6 +3286,8 @@
 		return 0;
 
 	l = sub->parent;
+	sub = l->activesub;
+	
 	if (sub->owner) {
 		if (sub->owner->_state == 0) {
 			f.frametype = AST_FRAME_DTMF_BEGIN;
@@ -3154,7 +3332,7 @@
 		ast_verbose("unknown1 in handle_stimulus_message is '%d'\n", unknown1);
 
 	sub = find_subchannel_by_instance_reference(d, d->lastlineinstance, d->lastcallreference);
-
+	
 	if (!sub) {
 		l = find_line_by_instance(d, d->lastlineinstance);
 		if (!l) {
@@ -3164,6 +3342,8 @@
 		l = sub->parent;
 	}
 
+	sub = l->activesub;
+
 	switch(event) {
 	case STIMULUS_REDIAL:
 		if (skinnydebug)
@@ -3245,21 +3425,12 @@
 	case STIMULUS_HOLD:
 		if (skinnydebug)
 			ast_verbose("Received Stimulus: Hold(%d)\n", instance);
-
-		if (!sub)
-			break;
-
-		if (sub->onhold) {
-			skinny_unhold(sub);
-		} else {
-			skinny_hold(sub);
-		}
+		handle_hold_button(sub);
 		break;
 	case STIMULUS_TRANSFER:
 		if (skinnydebug)
 			ast_verbose("Received Stimulus: Transfer(%d)\n", instance);
-		transmit_tone(s, SKINNY_DIALTONE);
-		/* XXX figure out how to transfer */
+		handle_transfer_button(sub);
 		break;
 	case STIMULUS_CONFERENCE:
 		if (skinnydebug)
@@ -3978,6 +4149,8 @@
 		l = find_line_by_instance(d, d->lastlineinstance);
 	}
 
+	sub = l->activesub;
+	
 	if (!l) {
 		if (skinnydebug)
 			ast_verbose("Received Softkey Event: %d(%d)\n", event, instance);
@@ -4070,21 +4243,12 @@
 	case SOFTKEY_HOLD:
 		if (skinnydebug)
 			ast_verbose("Received Softkey Event: Hold(%d)\n", instance);
-
-		if (sub) {
-			if (sub->onhold) {
-				skinny_unhold(sub);
-			} else {
-				skinny_hold(sub);
-			}
-		}
-				
+		handle_hold_button(sub);
 		break;
 	case SOFTKEY_TRNSFER:
 		if (skinnydebug)
 			ast_verbose("Received Softkey Event: Transfer(%d)\n", instance);
-		transmit_tone(s, SKINNY_DIALTONE);
-		/* XXX figure out how to transfer */
+		handle_transfer_button(sub);
 		break;
 	case SOFTKEY_CFWDALL:
 		if (skinnydebug)
@@ -4298,7 +4462,7 @@
 
 		sub = find_subchannel_by_instance_reference(d, lineInstance, callReference);
 
-		if (sub && (sub->owner->_state <  AST_STATE_UP)) {
+		if (sub && ((sub->owner->_state < AST_STATE_UP) || sub->onhold)) {
 			char dgt;
 			int digit = letohl(req->data.keypad.button);
 	
