Index: channels/chan_skinny.c
===================================================================
--- channels/chan_skinny.c	(revision 38220)
+++ channels/chan_skinny.c	(working copy)
@@ -734,6 +734,7 @@
 static char context[AST_MAX_CONTEXT] = "default";
 static char language[MAX_LANGUAGE] = "";
 static char mohinterpret[MAX_MUSICCLASS] = "default";
+static char mohsuggest[MAX_MUSICCLASS] = "";
 static char cid_num[AST_MAX_EXTENSION] = "";
 static char cid_name[AST_MAX_EXTENSION] = "";
 static char linelabel[AST_MAX_EXTENSION] ="";
@@ -888,6 +889,7 @@
 	/* time_t lastouttime; */			/* Unused */
 	int progress;
 	int ringing;
+	int onhold;
 	/* int lastout; */				/* Unused */
 	int cxmode;
 	int nat;
@@ -912,6 +914,7 @@
 	char call_forward[AST_MAX_EXTENSION];
 	char mailbox[AST_MAX_EXTENSION];
 	char mohinterpret[MAX_MUSICCLASS];
+	char mohsuggest[MAX_MUSICCLASS];
 	char lastnumberdialed[AST_MAX_EXTENSION];	/* Last number that was dialed - used for redial */
 	int curtone;					/* Current tone being played */
 	ast_group_t callgroup;
@@ -1848,6 +1851,8 @@
 				}
 			} else if (!strcasecmp(v->name, "mohinterpret") || !strcasecmp(v->name, "musiconhold")) {
 				ast_copy_string(mohinterpret, v->value, sizeof(mohinterpret));
+			} else if (!strcasecmp(v->name, "mohsuggest")) {
+				ast_copy_string(mohsuggest, v->value, sizeof(mohsuggest));
 			} else if (!strcasecmp(v->name, "callgroup")) {
 				cur_callergroup = ast_get_group(v->value);
 			} else if (!strcasecmp(v->name, "pickupgroup")) {
@@ -1913,6 +1918,7 @@
 					ast_copy_string(l->label, linelabel, sizeof(l->label));
 					ast_copy_string(l->language, language, sizeof(l->language));
 					ast_copy_string(l->mohinterpret, mohinterpret, sizeof(l->mohinterpret));
+					ast_copy_string(l->mohsuggest, mohsuggest, sizeof(l->mohsuggest));
 					ast_copy_string(l->mailbox, mailbox, sizeof(l->mailbox));
 					ast_copy_string(l->mailbox, mailbox, sizeof(l->mailbox));
 					if (!ast_strlen_zero(mailbox)) {
@@ -2542,6 +2548,7 @@
 			sub->cxmode = SKINNY_CX_INACTIVE;
 			sub->nat = l->nat;
 			sub->parent = l;
+			sub->onhold = 0;
 
 			sub->next = l->sub;
 			l->sub = sub;
@@ -2725,6 +2732,7 @@
 	struct skinny_subchannel *sub;
 	/*struct skinny_speeddial *sd;*/
 	struct ast_channel *c;
+	struct sockaddr_in us;
 	pthread_t t;
 	int event;
 	int instance;
@@ -2816,9 +2824,72 @@
 #endif
 		break;
 	case STIMULUS_HOLD:
-		/* start moh? set RTP to 0.0.0.0? */
 		if (skinnydebug)
 			ast_verbose("Received Stimulus: Hold(%d)\n", instance);
+
+		if (sub->onhold) {
+			/* Channel is on hold, so we will unhold */
+			if (skinnydebug)
+				ast_verbose("Taking off Hold(%d)\n", instance);
+
+			ast_queue_control(sub->owner, AST_CONTROL_UNHOLD);
+
+			if (!(req = req_alloc(sizeof(struct activate_call_plane_message), ACTIVATE_CALL_PLANE_MESSAGE)))
+				return 0;
+			req->data.activatecallplane.lineInstance = htolel(instance);
+			transmit_response(s, req);
+			if (!(req = req_alloc(sizeof(struct open_receive_channel_message), OPEN_RECEIVE_CHANNEL_MESSAGE)))
+				return 0;
+			req->data.openreceivechannel.conferenceId = htolel(0);
+			req->data.openreceivechannel.partyId = htolel(sub->callid);
+			req->data.openreceivechannel.packets = htolel(20);
+			req->data.openreceivechannel.capability = htolel(convert_cap(l->capability));
+			req->data.openreceivechannel.echo = htolel(0);
+			req->data.openreceivechannel.bitrate = htolel(0);
+			transmit_response(s, req);
+			ast_rtp_get_us(sub->rtp, &us);
+			if (!(req = req_alloc(sizeof(start_media_transmission_message), START_MEDIA_TRANSMISSION_MESSAGE)))
+				return -1;
+			req->data.startmedia.conferenceId = htolel(0);
+			req->data.startmedia.passThruPartyId = htolel(sub->callid);
+			req->data.startmedia.remoteIp = htolel(d->ourip.s_addr);
+			req->data.startmedia.remotePort = htolel(ntohs(us.sin_port));
+			req->data.startmedia.packetSize = htolel(20);
+			req->data.startmedia.payloadType = htolel(convert_cap(l->capability));
+			req->data.startmedia.qualifier.precedence = htolel(127);
+			req->data.startmedia.qualifier.vad = htolel(0);
+			req->data.startmedia.qualifier.packets = htolel(0);
+			req->data.startmedia.qualifier.bitRate = htolel(0);
+			transmit_response(s, req);
+			transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON);
+			sub->onhold = 0;
+		} else {
+			/* Channel needs to be put on hold */
+			if (skinnydebug)
+				ast_verbose("Putting on Hold(%d)\n", instance);
+
+			ast_queue_control_data(sub->owner, AST_CONTROL_HOLD,
+				S_OR(l->mohsuggest, NULL),
+				!ast_strlen_zero(l->mohsuggest) ? strlen(l->mohsuggest) + 1 : 0);
+
+			if (!(req = req_alloc(sizeof(struct activate_call_plane_message), ACTIVATE_CALL_PLANE_MESSAGE)))
+				return 0;
+			req->data.activatecallplane.lineInstance = htolel(instance);
+			transmit_response(s, req);
+			if (!(req = req_alloc(sizeof(struct close_receive_channel_message), CLOSE_RECEIVE_CHANNEL_MESSAGE)))
+				return 0;
+			req->data.closereceivechannel.conferenceId = htolel(0);
+			req->data.closereceivechannel.partyId = htolel(sub->callid);
+			transmit_response(s, req);
+			if (!(req = req_alloc(sizeof(struct stop_media_transmission_message), STOP_MEDIA_TRANSMISSION_MESSAGE)))
+				return 0;
+			req->data.stopmedia.conferenceId = htolel(0);
+			req->data.stopmedia.passThruPartyId = htolel(sub->callid);
+			transmit_response(s, req);
+			transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_BLINK);
+			sub->onhold = 1;
+		}
+				
 		break;
 	case STIMULUS_TRANSFER:
 		if (skinnydebug)
@@ -2961,6 +3032,12 @@
 		l = sub->parent;
 	}
 
+	if (sub && sub->onhold) {
+		transmit_ringer_mode(s, SKINNY_RING_OFF);
+		l->hookstate = SKINNY_OFFHOOK;
+		return 1;
+	}
+
 	transmit_ringer_mode(s, SKINNY_RING_OFF);
 	transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON);
 	l->hookstate = SKINNY_OFFHOOK;
@@ -3016,10 +3093,14 @@
 
 	if (!sub) {
 		return 0;
-	} else {
-		l = sub->parent;
 	}
+	l = sub->parent;
 
+	if (sub->onhold) {
+		l->hookstate = SKINNY_ONHOOK;
+		return 0;
+	}
+
 	if (l->hookstate == SKINNY_ONHOOK) {
 		/* Something else already put us back on hook */
 		return 0;
@@ -3387,6 +3468,7 @@
 	struct skinny_line *l;
 	struct skinny_subchannel *sub = NULL;
 	struct ast_channel *c;
+	struct sockaddr_in us;
 	pthread_t t;
 	int event;
 	int instance;
@@ -3484,9 +3566,74 @@
 		}
 		break;
 	case SOFTKEY_HOLD:
-		/* start moh? set RTP to 0.0.0.0? */
 		if (skinnydebug)
 			ast_verbose("Received Softkey Event: Hold(%d)\n", instance);
+
+		if (sub) {
+			if (sub->onhold) {
+				/* Channel is on hold, so we will unhold */
+				if (skinnydebug)
+					ast_verbose("Taking off Hold(%d)\n", instance);
+
+				ast_queue_control(sub->owner, AST_CONTROL_UNHOLD);
+
+				if (!(req = req_alloc(sizeof(struct activate_call_plane_message), ACTIVATE_CALL_PLANE_MESSAGE)))
+					return 0;
+				req->data.activatecallplane.lineInstance = htolel(instance);
+				transmit_response(s, req);
+				if (!(req = req_alloc(sizeof(struct open_receive_channel_message), OPEN_RECEIVE_CHANNEL_MESSAGE)))
+					return 0;
+				req->data.openreceivechannel.conferenceId = htolel(0);
+				req->data.openreceivechannel.partyId = htolel(sub->callid);
+				req->data.openreceivechannel.packets = htolel(20);
+				req->data.openreceivechannel.capability = htolel(convert_cap(l->capability));
+				req->data.openreceivechannel.echo = htolel(0);
+				req->data.openreceivechannel.bitrate = htolel(0);
+				transmit_response(s, req);
+				ast_rtp_get_us(sub->rtp, &us);
+				if (!(req = req_alloc(sizeof(start_media_transmission_message), START_MEDIA_TRANSMISSION_MESSAGE)))
+					return -1;
+				req->data.startmedia.conferenceId = htolel(0);
+				req->data.startmedia.passThruPartyId = htolel(sub->callid);
+				req->data.startmedia.remoteIp = htolel(d->ourip.s_addr);
+				req->data.startmedia.remotePort = htolel(ntohs(us.sin_port));
+				req->data.startmedia.packetSize = htolel(20);
+				req->data.startmedia.payloadType = htolel(convert_cap(l->capability));
+				req->data.startmedia.qualifier.precedence = htolel(127);
+				req->data.startmedia.qualifier.vad = htolel(0);
+				req->data.startmedia.qualifier.packets = htolel(0);
+				req->data.startmedia.qualifier.bitRate = htolel(0);
+				transmit_response(s, req);
+				transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON);
+				sub->onhold = 0;
+			} else {
+				/* Channel needs to be put on hold */
+				if (skinnydebug)
+					ast_verbose("Putting on Hold(%d)\n", instance);
+
+				ast_queue_control_data(sub->owner, AST_CONTROL_HOLD,
+					S_OR(l->mohsuggest, NULL),
+					!ast_strlen_zero(l->mohsuggest) ? strlen(l->mohsuggest) + 1 : 0);
+
+				if (!(req = req_alloc(sizeof(struct activate_call_plane_message), ACTIVATE_CALL_PLANE_MESSAGE)))
+					return 0;
+				req->data.activatecallplane.lineInstance = htolel(instance);
+				transmit_response(s, req);
+				if (!(req = req_alloc(sizeof(struct close_receive_channel_message), CLOSE_RECEIVE_CHANNEL_MESSAGE)))
+					return 0;
+				req->data.closereceivechannel.conferenceId = htolel(0);
+				req->data.closereceivechannel.partyId = htolel(sub->callid);
+				transmit_response(s, req);
+				if (!(req = req_alloc(sizeof(struct stop_media_transmission_message), STOP_MEDIA_TRANSMISSION_MESSAGE)))
+					return 0;
+				req->data.stopmedia.conferenceId = htolel(0);
+				req->data.stopmedia.passThruPartyId = htolel(sub->callid);
+				transmit_response(s, req);
+				transmit_lamp_indication(s, STIMULUS_LINE, l->instance, SKINNY_LAMP_BLINK);
+				sub->onhold = 1;
+			}
+		}
+				
 		break;
 	case SOFTKEY_TRNSFER:
 		if (skinnydebug)
