Index: configs/skinny.conf.sample
===================================================================
--- configs/skinny.conf.sample	(revision 388998)
+++ configs/skinny.conf.sample	(working copy)
@@ -100,6 +100,7 @@
 ;                            ; (earlyrtp=1) or device generated (earlyrtp=0). default=yes
 ;transfer=1                  ; whether the device is allowed to transfer. default=yes
 ;context=default             ; context to use for this line.
+;callfwdtimeout=20           ; sets number of seconds before cfwd_noans occurs
 ;------------------------------- SPECIFIC LINE OPTIONS -----------------------------
 ;setvar=        	     ; allows for the setting of chanvars.
 ;-----------------------------------------------------------------------------------
Index: channels/chan_skinny.c
===================================================================
--- channels/chan_skinny.c	(revision 388998)
+++ channels/chan_skinny.c	(working copy)
@@ -919,6 +919,7 @@
 	SOFTKEY_NEWCALL,
 	SOFTKEY_CFWDALL,
 	SOFTKEY_CFWDBUSY,
+	SOFTKEY_CFWDNOANSWER,
 	SOFTKEY_DND,
 	SOFTKEY_GPICKUP,
 	/*SOFTKEY_CONFRN,*/
@@ -931,6 +932,7 @@
 	SOFTKEY_PARK,
 	SOFTKEY_CFWDALL,
 	SOFTKEY_CFWDBUSY,
+	SOFTKEY_CFWDNOANSWER,
 };
 
 static const uint8_t soft_key_default_onhold[] = {
@@ -951,6 +953,7 @@
 	SOFTKEY_ENDCALL,
 	SOFTKEY_CFWDALL,
 	SOFTKEY_CFWDBUSY,
+	SOFTKEY_CFWDNOANSWER,
 	SOFTKEY_GPICKUP,
 };
 
@@ -961,6 +964,7 @@
 	SOFTKEY_PARK,
 	SOFTKEY_CFWDALL,
 	SOFTKEY_CFWDBUSY,
+	SOFTKEY_CFWDNOANSWER,
 };
 
 static const uint8_t soft_key_default_dadfd[] = {
@@ -1361,6 +1365,9 @@
 #define SUBSTATE_PROGRESS 12
 #define SUBSTATE_DIALING 101
 
+#define DIALTYPE_NORMAL      1<<0
+#define DIALTYPE_CFWD        1<<1
+
 struct skinny_subchannel {
 	ast_mutex_t lock;
 	struct ast_channel *owner;
@@ -1382,6 +1389,9 @@
 	int aa_beep;
 	int aa_mute;
 	int dialer_sched;
+	int cfwd_sched;
+	int dialType;
+	int getforward;
 	char *origtonum;
 	char *origtoname;
 
@@ -1424,7 +1434,7 @@
 	int threewaycalling;				\
 	int mwiblink;					\
 	int cancallforward;				\
-	int getforward;					\
+	int callfwdtimeout;				\
 	int dnd;					\
 	int hidecallerid;				\
 	int amaflags;					\
@@ -1467,7 +1477,7 @@
 	.instance = 0,
 	.directmedia = 0,
 	.nat = 0,
-	.getforward = 0,
+	.callfwdtimeout = 20,
 	.prune = 0,
 };
 static struct skinny_line_options *default_line = &default_line_struct;
@@ -2257,7 +2267,7 @@
 				l->prefs = d->prefs; */
 				l->instance = instance;
 				l->newmsgs = ast_app_has_voicemail(l->mailbox, NULL);
-				set_callforwards(l, NULL, 0);
+				set_callforwards(l, NULL, SKINNY_CFWD_ALL|SKINNY_CFWD_BUSY|SKINNY_CFWD_NOANSWER);
 				manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: Skinny\r\nPeer: Skinny/%s@%s\r\nPeerStatus: Registered\r\n", l->name, d->name);
 				register_exten(l);
 				/* initialize MWI on line and device */
@@ -4882,6 +4892,17 @@
 	return 0;
 }
 
+static int skinny_cfwd_cb(const void *data)
+{
+	struct skinny_subchannel *sub = (struct skinny_subchannel *)data;
+	struct skinny_line *l = sub->line;
+	sub->cfwd_sched = 0;
+	SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %d - CFWDNOANS to %s.\n", sub->callid, l->call_forward_noanswer);
+	ast_channel_call_forward_set(sub->owner, l->call_forward_noanswer);
+	ast_queue_control(sub->owner, AST_CONTROL_REDIRECTING);
+	return 0;
+}
+
 static int skinny_call(struct ast_channel *ast, const char *dest, int timeout)
 {
 	int res = 0;
@@ -5380,6 +5401,9 @@
 			sub->calldirection = direction;
 			sub->aa_sched = 0;
 			sub->dialer_sched = 0;
+			sub->cfwd_sched = 0;
+			sub->dialType = DIALTYPE_NORMAL;
+			sub->getforward = 0;
 
 			if (subline) {
 				sub->subline = subline;
@@ -5429,13 +5453,17 @@
 		ast_channel_named_callgroups_set(tmp, l->named_callgroups);
 		ast_channel_named_pickupgroups_set(tmp, l->named_pickupgroups);
 
-		/* XXX Need to figure out how to handle CFwdNoAnswer */
 		if (l->cfwdtype & SKINNY_CFWD_ALL) {
+			SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %d - CFWDALL to %s.\n", sub->callid, l->call_forward_all);
 			ast_channel_call_forward_set(tmp, l->call_forward_all);
 		} else if (l->cfwdtype & SKINNY_CFWD_BUSY) {
 			if (get_devicestate(l) != AST_DEVICE_NOT_INUSE) {
+				SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %d - CFWDBUSY to %s.\n", sub->callid, l->call_forward_busy);
 				ast_channel_call_forward_set(tmp, l->call_forward_busy);
 			}
+		} else if (l->cfwdtype & SKINNY_CFWD_NOANSWER) {
+			SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %d - CFWDNOANS Scheduling for %d seconds.\n", sub->callid, 20);
+			sub->cfwd_sched = skinny_sched_add(l->callfwdtimeout, skinny_cfwd_cb, sub);
 		}
 
 		if (subline) {
@@ -5535,6 +5563,19 @@
 		sub->aa_mute = 0;
 	}
 
+	if (sub->cfwd_sched) {
+		if (state == SUBSTATE_CONNECTED) {
+			if (skinny_sched_del(sub->cfwd_sched, sub)) {
+				SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %d - trying to change state from %s to %s, but already forwarded because no answer.\n",
+					sub->callid, substate2str(sub->substate), substate2str(actualstate));
+				return;
+			}
+			sub->cfwd_sched = 0;
+		} else if (state == SUBSTATE_ONHOOK) {
+			skinny_sched_del(sub->cfwd_sched, sub);
+		}
+	}
+
 	if ((state == SUBSTATE_RINGIN) && ((d->hookstate == SKINNY_OFFHOOK) || (AST_LIST_NEXT(AST_LIST_FIRST(&l->sub), list)))) {
 		actualstate = SUBSTATE_CALLWAIT;
 	}
@@ -5665,8 +5706,6 @@
 		sub->callid, substate2str(sub->substate), substate2str(actualstate));
 
 	if (actualstate == sub->substate) {
-		send_callinfo(sub);
-		transmit_callstate(d, l->instance, sub->callid, SKINNY_HOLD);
 		return;
 	}
 
@@ -6001,26 +6040,19 @@
 
 static void dialandactivatesub(struct skinny_subchannel *sub, char exten[AST_MAX_EXTENSION])
 {
-	if (sub->line->getforward) {
-		struct skinny_line *l = sub->line;
-		struct skinny_device *d = l->device;
-
-		// FIXME: needs some love and remove sleeps
-		SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %d - Set callforward to %s\n", sub->callid, exten);
-		set_callforwards(l, sub->exten, l->getforward);
-		transmit_start_tone(d, SKINNY_DIALTONE, l->instance, sub->callid);
-		transmit_lamp_indication(d, STIMULUS_FORWARDALL, 1, SKINNY_LAMP_ON);
-		transmit_displaynotify(d, "CFwd enabled", 10);
-		transmit_cfwdstate(d, l);
-		ast_safe_sleep(sub->owner, 500);
-		ast_indicate(sub->owner, -1);
-		ast_safe_sleep(sub->owner, 1000);
-		l->getforward = 0;
-		dumpsub(sub, 0);
-	} else {
+	struct skinny_line *l = sub->line;
+	struct skinny_device *d = l->device;
+	
+	if (sub->dialType == DIALTYPE_NORMAL) {
 		SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %d - Dial %s and Activate\n", sub->callid, exten);
 		ast_copy_string(sub->exten, exten, sizeof(sub->exten));
 		activatesub(sub, SUBSTATE_DIALING);
+	} else if (sub->dialType == DIALTYPE_CFWD) {
+		SKINNY_DEBUG(DEBUG_SUB, 3, "Sub %d - Set callforward(%d) to %s\n", sub->callid, sub->getforward, exten);
+		set_callforwards(l, sub->exten, sub->getforward);
+		dumpsub(sub, 1);
+		transmit_cfwdstate(d, l);
+		transmit_displaynotify(d, "CFwd enabled", 10);
 	}
 }
 
@@ -6099,48 +6131,38 @@
 	return 0;
 }
 
-static int handle_callforward_button(struct skinny_subchannel *sub, int cfwdtype)
+static void handle_callforward_button(struct skinny_line *l, struct skinny_subchannel *sub, int cfwdtype)
 {
-	struct skinny_line *l = sub->line;
 	struct skinny_device *d = l->device;
-	struct ast_channel *c = sub->owner;
+	struct ast_channel *c;
 
 	if (!d->session) {
 		ast_log(LOG_WARNING, "Device for line %s is not registered.\n", l->name);
-		return 0;
+		return;
 	}
 
-	if (d->hookstate == SKINNY_ONHOOK) {
-		d->hookstate = SKINNY_OFFHOOK;
-		transmit_speaker_mode(d, SKINNY_SPEAKERON);
-		transmit_callstate(d, l->instance, sub->callid, SKINNY_OFFHOOK);
-		transmit_activatecallplane(d, l);
-	}
-	transmit_clear_display_message(d, l->instance, sub->callid);
-
-	if (l->cfwdtype & cfwdtype) {
+	if (!sub && (l->cfwdtype & cfwdtype)) {
 		set_callforwards(l, NULL, cfwdtype);
-		ast_safe_sleep(c, 500);
-		transmit_speaker_mode(d, SKINNY_SPEAKEROFF);
-		transmit_closereceivechannel(d, sub);
-		transmit_stopmediatransmission(d, sub);
-		transmit_speaker_mode(d, SKINNY_SPEAKEROFF);
-		transmit_clearpromptmessage(d, l->instance, sub->callid);
-		transmit_callstate(d, l->instance, sub->callid, SKINNY_ONHOOK);
-		transmit_selectsoftkeys(d, 0, 0, KEYDEF_ONHOOK, KEYMASK_ALL);
-		transmit_activatecallplane(d, l);
-		transmit_displaynotify(d, "CFwd disabled", 10);
-		if (sub->owner && ast_channel_state(sub->owner) != AST_STATE_UP) {
-			ast_indicate(c, -1);
-			ast_hangup(c);
+		if (sub) {
+			dumpsub(sub, 1);
 		}
 		transmit_cfwdstate(d, l);
+		transmit_displaynotify(d, "CFwd disabled", 10);
 	} else {
-		l->getforward = cfwdtype;
-		setsubstate(sub, SUBSTATE_OFFHOOK);
+		if (!sub || !sub->owner) {
+			if (!(c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING))) {
+				ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
+				return;
+			}
+			sub = ast_channel_tech_pvt(c);
+			l->activesub = sub;
+			setsubstate(sub, SUBSTATE_OFFHOOK);
+		}
+		sub->getforward |= cfwdtype;
+		sub->dialType = DIALTYPE_CFWD;
 	}
-	return 0;
 }
+
 static int handle_ip_port_message(struct skinny_req *req, struct skinnysession *s)
 {
 	/* no response necessary */
@@ -6400,55 +6422,17 @@
 	case STIMULUS_FORWARDALL:
 		SKINNY_DEBUG(DEBUG_PACKET, 3, "Received STIMULUS_FORWARDALL from %s, inst %d, callref %d\n",
 			d->name, instance, callreference);
-
-		if (!sub || !sub->owner) {
-			c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
-		} else {
-			c = sub->owner;
-		}
-
-		if (!c) {
-			ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
-		} else {
-			sub = ast_channel_tech_pvt(c);
-			handle_callforward_button(sub, SKINNY_CFWD_ALL);
-		}
+		handle_callforward_button(l, sub, SKINNY_CFWD_ALL);
 		break;
 	case STIMULUS_FORWARDBUSY:
 		SKINNY_DEBUG(DEBUG_PACKET, 3, "Received STIMULUS_FORWARDBUSY from %s, inst %d, callref %d\n",
 			d->name, instance, callreference);
-
-		if (!sub || !sub->owner) {
-			c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
-		} else {
-			c = sub->owner;
-		}
-
-		if (!c) {
-			ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
-		} else {
-			sub = ast_channel_tech_pvt(c);
-			handle_callforward_button(sub, SKINNY_CFWD_BUSY);
-		}
+		handle_callforward_button(l, sub, SKINNY_CFWD_BUSY);
 		break;
 	case STIMULUS_FORWARDNOANSWER:
 		SKINNY_DEBUG(DEBUG_PACKET, 3, "Received STIMULUS_FORWARDNOANSWER from %s, inst %d, callref %d\n",
 			d->name, instance, callreference);
-
-#if 0 /* Not sure how to handle this yet */
-		if (!sub || !sub->owner) {
-			c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
-		} else {
-			c = sub->owner;
-		}
-
-		if (!c) {
-			ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
-		} else {
-			sub = c->tech_pvt;
-			handle_callforward_button(sub, SKINNY_CFWD_NOANSWER);
-		}
-#endif
+		handle_callforward_button(l, sub, SKINNY_CFWD_NOANSWER);
 		break;
 	case STIMULUS_DISPLAY:
 		/* Not sure what this is */
@@ -7037,58 +7021,17 @@
 	case SOFTKEY_CFWDALL:
 		SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_CFWDALL from %s, inst %d, callref %d\n",
 			d->name, instance, callreference);
-
-		if (!sub || !sub->owner) {
-			c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
-		} else {
-			c = sub->owner;
-		}
-
-		if (!c) {
-			ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
-		} else {
-			sub = ast_channel_tech_pvt(c);
-			l->activesub = sub;
-			handle_callforward_button(sub, SKINNY_CFWD_ALL);
-		}
+		handle_callforward_button(l, sub, SKINNY_CFWD_ALL);
 		break;
 	case SOFTKEY_CFWDBUSY:
 		SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_CFWDBUSY from %s, inst %d, callref %d\n",
 			d->name, instance, callreference);
-
-		if (!sub || !sub->owner) {
-			c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
-		} else {
-			c = sub->owner;
-		}
-
-		if (!c) {
-			ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
-		} else {
-			sub = ast_channel_tech_pvt(c);
-			l->activesub = sub;
-			handle_callforward_button(sub, SKINNY_CFWD_BUSY);
-		}
+		handle_callforward_button(l, sub, SKINNY_CFWD_BUSY);
 		break;
 	case SOFTKEY_CFWDNOANSWER:
 		SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_CFWDNOANSWER from %s, inst %d, callref %d\n",
 			d->name, instance, callreference);
-
-#if 0 /* Not sure how to handle this yet */
-		if (!sub || !sub->owner) {
-			c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
-		} else {
-			c = sub->owner;
-		}
-
-		if (!c) {
-			ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
-		} else {
-			sub = c->tech_pvt;
-			l->activesub = sub;
-			handle_callforward_button(sub, SKINNY_CFWD_NOANSWER);
-		}
-#endif
+		handle_callforward_button(l, sub, SKINNY_CFWD_NOANSWER);
 		break;
 	case SOFTKEY_BKSPC:
 		SKINNY_DEBUG(DEBUG_PACKET, 3, "Received SOFTKEY_BKSPC from %s, inst %d, callref %d\n",
@@ -7950,6 +7893,11 @@
 				CLINE_OPTS->cancallforward = ast_true(v->value);
 				continue;
 			}
+		} else if (!strcasecmp(v->name, "cfwdtimeout")) {
+			if (type & (TYPE_DEF_LINE | TYPE_LINE)) {
+				CLINE_OPTS->callfwdtimeout = atoi(v->value) * 1000;
+				continue;
+			}
 		} else if (!strcasecmp(v->name, "mailbox")) {
 			if (type & (TYPE_DEF_LINE | TYPE_LINE)) {
 				ast_copy_string(CLINE_OPTS->mailbox, v->value, sizeof(CLINE_OPTS->mailbox));
