Add SLA to skinny.
authorDamien Wedhorn <voip@facts.com.au>
Fri, 15 Jul 2011 08:19:46 +0000 (08:19 +0000)
committerDamien Wedhorn <voip@facts.com.au>
Fri, 15 Jul 2011 08:19:46 +0000 (08:19 +0000)
Adds sublines to skinny lines. Each subline can be attached to an
SLA station/trunk combo. Includes the following functionality:

Callid is persistent for both in/out calls on all skinny devices.
Can join, hold, resume.
All sublines appear under a single line button.

See: https://wiki.asterisk.org/wiki/display/~wedhorn/Skinny+SLA for doc.

(closes issue ASTERISK-17947)

Review: https://reviewboard.asterisk.org/r/1239/

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@328381 65c4cc65-6c06-0410-ace0-fbb531ad65f3

channels/chan_skinny.c

index 3864091..dad151e 100644 (file)
@@ -255,6 +255,9 @@ AST_THREADSTORAGE(control2str_threadbuf);
 AST_THREADSTORAGE(substate2str_threadbuf);
 #define SUBSTATE2STR_BUFSIZE   15
 
+AST_THREADSTORAGE(callstate2str_threadbuf);
+#define CALLSTATE2STR_BUFSIZE   15
+
 /*********************
  * Protocol Messages *
  *********************/
@@ -639,6 +642,8 @@ struct soft_key_template_definition {
 #define KEYDEF_RINGOUT 8
 #define KEYDEF_OFFHOOKWITHFEAT 9
 #define KEYDEF_UNKNOWN 10
+#define KEYDEF_SLAHOLD 11
+#define KEYDEF_SLACONNECTEDNOTACTIVE 12
 
 #define SOFTKEY_NONE 0x00
 #define SOFTKEY_REDIAL 0x01
@@ -898,6 +903,18 @@ static const uint8_t soft_key_default_unknown[] = {
        SOFTKEY_NONE,
 };
 
+static const uint8_t soft_key_default_SLAhold[] = {
+       SOFTKEY_REDIAL,
+       SOFTKEY_NEWCALL,
+       SOFTKEY_RESUME,
+};
+
+static const uint8_t soft_key_default_SLAconnectednotactive[] = {
+       SOFTKEY_REDIAL,
+       SOFTKEY_NEWCALL,
+       SOFTKEY_JOIN,
+};
+
 static const struct soft_key_definitions soft_key_default_definitions[] = {
        {KEYDEF_ONHOOK, soft_key_default_onhook, sizeof(soft_key_default_onhook) / sizeof(uint8_t)},
        {KEYDEF_CONNECTED, soft_key_default_connected, sizeof(soft_key_default_connected) / sizeof(uint8_t)},
@@ -909,7 +926,9 @@ static const struct soft_key_definitions soft_key_default_definitions[] = {
        {KEYDEF_CONNWITHCONF, soft_key_default_connwithconf, sizeof(soft_key_default_connwithconf) / sizeof(uint8_t)},
        {KEYDEF_RINGOUT, soft_key_default_ringout, sizeof(soft_key_default_ringout) / sizeof(uint8_t)},
        {KEYDEF_OFFHOOKWITHFEAT, soft_key_default_offhookwithfeat, sizeof(soft_key_default_offhookwithfeat) / sizeof(uint8_t)},
-       {KEYDEF_UNKNOWN, soft_key_default_unknown, sizeof(soft_key_default_unknown) / sizeof(uint8_t)}
+       {KEYDEF_UNKNOWN, soft_key_default_unknown, sizeof(soft_key_default_unknown) / sizeof(uint8_t)},
+       {KEYDEF_SLAHOLD, soft_key_default_SLAhold, sizeof(soft_key_default_SLAhold) / sizeof(uint8_t)},
+       {KEYDEF_SLACONNECTEDNOTACTIVE, soft_key_default_SLAconnectednotactive, sizeof(soft_key_default_SLAconnectednotactive) / sizeof(uint8_t)}
 };
 
 struct soft_key_template_res_message {
@@ -1221,6 +1240,7 @@ struct skinny_subchannel {
        AST_LIST_ENTRY(skinny_subchannel) list;
        struct skinny_subchannel *related;
        struct skinny_line *line;
+       struct skinny_subline *subline;
 };
 
 #define SKINNY_LINE_OPTIONS                            \
@@ -1245,6 +1265,8 @@ struct skinny_subchannel {
        char mohinterpret[MAX_MUSICCLASS];              \
        char mohsuggest[MAX_MUSICCLASS];                \
        char lastnumberdialed[AST_MAX_EXTENSION];       \
+       char dialoutexten[AST_MAX_EXTENSION];           \
+       char dialoutcontext[AST_MAX_CONTEXT];           \
        ast_group_t callgroup;                          \
        ast_group_t pickupgroup;                        \
        int callwaiting;                                \
@@ -1271,9 +1293,11 @@ struct skinny_subchannel {
 struct skinny_line {
        SKINNY_LINE_OPTIONS
        ast_mutex_t lock;
+       struct skinny_container *container;
        struct ast_event_sub *mwi_event_sub; /* Event based MWI */
        struct skinny_subchannel *activesub;
        AST_LIST_HEAD(, skinny_subchannel) sub;
+       AST_LIST_HEAD(, skinny_subline) sublines;
        AST_LIST_ENTRY(skinny_line) list;
        AST_LIST_ENTRY(skinny_line) all;
        struct skinny_device *device;
@@ -1300,8 +1324,29 @@ static struct skinny_line_options *default_line = &default_line_struct;
 
 static AST_LIST_HEAD_STATIC(lines, skinny_line);
 
+struct skinny_subline {
+       struct skinny_container *container;
+       struct skinny_line *line;
+       struct skinny_subchannel *sub;
+       AST_LIST_ENTRY(skinny_subline) list;
+       char name[80];
+       char context[AST_MAX_CONTEXT];
+       char exten[AST_MAX_EXTENSION];
+       char stname[AST_MAX_EXTENSION];
+       char lnname[AST_MAX_EXTENSION];
+       char ourName[40];
+       char ourNum[24];
+       char theirName[40];
+       char theirNum[24];
+       int calldirection;
+       int substate;
+       int extenstate;
+       unsigned int callid;
+};
+
 struct skinny_speeddial {
        ast_mutex_t lock;
+       struct skinny_container *container;
        char label[42];
        char context[AST_MAX_CONTEXT];
        char exten[AST_MAX_EXTENSION];
@@ -1314,6 +1359,16 @@ struct skinny_speeddial {
        struct skinny_device *parent;
 };
 
+#define SKINNY_DEVICECONTAINER 1
+#define SKINNY_LINECONTAINER 2
+#define SKINNY_SUBLINECONTAINER 3
+#define SKINNY_SDCONTAINER 4
+
+struct skinny_container {
+       int type;
+       void *data;
+};
+
 struct skinny_addon {
        ast_mutex_t lock;
        char type[10];
@@ -1447,6 +1502,7 @@ static struct skinny_line *skinny_line_destroy(struct skinny_line *l)
 {
        l->cap = ast_format_cap_destroy(l->cap);
        l->confcap = ast_format_cap_destroy(l->confcap);
+       ast_free(l->container);
        ast_free(l);
        return NULL;
 }
@@ -1695,6 +1751,47 @@ static struct skinny_line *find_line_by_name(const char *dest)
        return tmpl;
 }
 
+static struct skinny_subline *find_subline_by_name(const char *dest)
+{
+       struct skinny_line *l;
+       struct skinny_subline *subline;
+       struct skinny_subline *tmpsubline = NULL;
+       struct skinny_device *d;
+
+       AST_LIST_LOCK(&devices);
+       AST_LIST_TRAVERSE(&devices, d, list){
+               AST_LIST_TRAVERSE(&d->lines, l, list){
+                       AST_LIST_TRAVERSE(&l->sublines, subline, list){
+                               if (!strcasecmp(subline->name, dest)) {
+                                       if (tmpsubline) {
+                                               ast_verb(2, "Ambiguous subline name: %s\n", dest);
+                                               AST_LIST_UNLOCK(&devices);
+                                               return NULL;
+                                       } else
+                                               tmpsubline = subline;
+                               }
+                       }
+               }
+       }
+       AST_LIST_UNLOCK(&devices);
+       return tmpsubline;
+}
+
+static struct skinny_subline *find_subline_by_callid(struct skinny_device *d, int callid)
+{
+       struct skinny_subline *subline;
+       struct skinny_line *l;
+       
+       AST_LIST_TRAVERSE(&d->lines, l, list){
+               AST_LIST_TRAVERSE(&l->sublines, subline, list){
+                       if (subline->callid == callid) {
+                               return subline;
+                       }
+               }
+       }
+       return NULL;
+}
+
 /*!
  * implement the setvar config line
  */
@@ -1946,6 +2043,7 @@ static int skinny_register(struct skinny_req *req, struct skinnysession *s)
 {
        struct skinny_device *d;
        struct skinny_line *l;
+       struct skinny_subline *subline;
        struct skinny_speeddial *sd;
        struct sockaddr_in sin;
        socklen_t slen;
@@ -1973,7 +2071,7 @@ static int skinny_register(struct skinny_req *req, struct skinnysession *s)
                        d->ourip = sin.sin_addr;
 
                        AST_LIST_TRAVERSE(&d->speeddials, sd, list) {
-                               sd->stateid = ast_extension_state_add(sd->context, sd->exten, skinny_extensionstate_cb, sd);
+                               sd->stateid = ast_extension_state_add(sd->context, sd->exten, skinny_extensionstate_cb, sd->container);
                        }
                        instance = 0;
                        AST_LIST_TRAVERSE(&d->lines, l, list) {
@@ -2000,6 +2098,9 @@ static int skinny_register(struct skinny_req *req, struct skinnysession *s)
                                        register_exten(l);
                                        /* initialize MWI on line and device */
                                        mwi_event_cb(0, l);
+                                       AST_LIST_TRAVERSE(&l->sublines, subline, list) {
+                                               ast_extension_state_add(subline->context, subline->exten, skinny_extensionstate_cb, subline->container);
+                                       }
                                        ast_devstate_changed(AST_DEVICE_NOT_INUSE, "Skinny/%s", l->name);
                                }
                                --instance;
@@ -2175,6 +2276,40 @@ static char *message2str(int type)
                return tmp;
        }
 }
+
+static char *callstate2str(int ind)
+{
+       char *tmp;
+
+       switch (ind) {
+       case SUBSTATE_OFFHOOK:
+               return "SKINNY_OFFHOOK";
+       case SKINNY_ONHOOK:
+               return "SKINNY_ONHOOK";
+       case SKINNY_RINGOUT:
+               return "SKINNY_RINGOUT";
+       case SKINNY_RINGIN:
+               return "SKINNY_RINGIN";
+       case SKINNY_CONNECTED:
+               return "SKINNY_CONNECTED";
+       case SKINNY_BUSY:
+               return "SKINNY_BUSY";
+       case SKINNY_CONGESTION:
+               return "SKINNY_CONGESTION";
+       case SKINNY_PROGRESS:
+               return "SKINNY_PROGRESS";
+       case SKINNY_HOLD:
+               return "SKINNY_HOLD";
+       case SKINNY_CALLWAIT:
+               return "SKINNY_CALLWAIT";
+       default:
+               if (!(tmp = ast_threadstorage_get(&callstate2str_threadbuf, CALLSTATE2STR_BUFSIZE)))
+                        return "Unknown";
+               snprintf(tmp, CALLSTATE2STR_BUFSIZE, "UNKNOWN-%d", ind);
+               return tmp;
+       }
+}
+
 #endif
 
 static int transmit_response_bysession(struct skinnysession *s, struct skinny_req *req)
@@ -2258,24 +2393,46 @@ static void transmit_microphone_mode(struct skinny_device *d, int mode)
        transmit_response(d, req);
 }
 
-static void transmit_callinfo(struct skinny_subchannel *sub)
+//static void transmit_callinfo(struct skinny_subchannel *sub)
+static void transmit_callinfo(struct skinny_device *d, int instance, int callid, char *fromname, char *fromnum, char *toname, char *tonum, int calldirection)
 {
-       struct skinny_device *d;
-       struct skinny_line *l;
        struct skinny_req *req;
+
+       if (!(req = req_alloc(sizeof(struct call_info_message), CALL_INFO_MESSAGE)))
+               return;
+
+       if (skinnydebug) {
+               ast_verb(1, "Setting Callinfo to %s(%s) from %s(%s) (dir=%d) on %s(%d)\n", toname, tonum, fromname, fromnum, calldirection, d->name, instance);
+       }
+       
+       ast_copy_string(req->data.callinfo.callingPartyName, fromname, sizeof(req->data.callinfo.callingPartyName));
+       ast_copy_string(req->data.callinfo.callingParty, fromnum, sizeof(req->data.callinfo.callingParty));
+       ast_copy_string(req->data.callinfo.calledPartyName, toname, sizeof(req->data.callinfo.calledPartyName));
+       ast_copy_string(req->data.callinfo.calledParty, tonum, sizeof(req->data.callinfo.calledParty));
+       req->data.callinfo.instance = htolel(instance);
+       req->data.callinfo.reference = htolel(callid);
+       req->data.callinfo.type = htolel(calldirection);
+       transmit_response(d, req);
+}
+
+static void send_callinfo(struct skinny_subchannel *sub)
+{
        struct ast_channel *ast;
+       struct skinny_device *d;
+       struct skinny_line *l;
        char *fromname;
        char *fromnum;
        char *toname;
        char *tonum;
 
-       if (!sub || !(l=sub->line) || !(d=l->device) || !(ast=sub->owner)) {
+       if (!sub || !sub->owner || !sub->line || !sub->line->device) {
                return;
        }
-
-       if (!(req = req_alloc(sizeof(struct call_info_message), CALL_INFO_MESSAGE)))
-               return;
-
+       
+       ast = sub->owner;
+       l = sub->line;
+       d = l->device;
+       
        if (sub->calldirection == SKINNY_INCOMING) {
                fromname = S_COR(ast->connected.id.name.valid, ast->connected.id.name.str, "");
                fromnum = S_COR(ast->connected.id.number.valid, ast->connected.id.number.str, "");
@@ -2290,19 +2447,42 @@ static void transmit_callinfo(struct skinny_subchannel *sub)
                ast_verb(1, "Error sending Callinfo to %s(%d) - No call direction in sub\n", d->name, l->instance);
                return;
        }
+       transmit_callinfo(d, l->instance, sub->callid, fromname, fromnum, toname, tonum, sub->calldirection);
+}
 
-       if (skinnydebug) {
-               ast_verb(1, "Setting Callinfo to %s(%s) from %s(%s) (dir=%d) on %s(%d)\n", toname, tonum, fromname, fromnum, sub->calldirection, d->name, l->instance);
+static void push_callinfo(struct skinny_subline *subline, struct skinny_subchannel *sub)
+{
+       struct ast_channel *ast;
+       struct skinny_device *d;
+       struct skinny_line *l;
+       char *fromname;
+       char *fromnum;
+       char *toname;
+       char *tonum;
+
+       if (!sub || !sub->owner || !sub->line || !sub->line->device) {
+               return;
        }
        
-       ast_copy_string(req->data.callinfo.callingPartyName, fromname, sizeof(req->data.callinfo.callingPartyName));
-       ast_copy_string(req->data.callinfo.callingParty, fromnum, sizeof(req->data.callinfo.callingParty));
-       ast_copy_string(req->data.callinfo.calledPartyName, toname, sizeof(req->data.callinfo.calledPartyName));
-       ast_copy_string(req->data.callinfo.calledParty, tonum, sizeof(req->data.callinfo.calledParty));
-       req->data.callinfo.instance = htolel(l->instance);
-       req->data.callinfo.reference = htolel(sub->callid);
-       req->data.callinfo.type = htolel(sub->calldirection);
-       transmit_response(d, req);
+       ast = sub->owner;
+       l = sub->line;
+       d = l->device;
+       
+       if (sub->calldirection == SKINNY_INCOMING) {
+               fromname = S_COR(ast->connected.id.name.valid, ast->connected.id.name.str, "");
+               fromnum = S_COR(ast->connected.id.number.valid, ast->connected.id.number.str, "");
+               toname = S_COR(ast->caller.id.name.valid, ast->caller.id.name.str, "");
+               tonum = S_COR(ast->caller.id.number.valid, ast->caller.id.number.str, "");
+       } else if (sub->calldirection == SKINNY_OUTGOING) {
+               fromname = S_COR(ast->caller.id.name.valid, ast->caller.id.name.str, "");
+               fromnum = S_COR(ast->caller.id.number.valid, ast->caller.id.number.str, "");
+               toname = S_COR(ast->connected.id.name.valid, ast->connected.id.name.str, l->lastnumberdialed);
+               tonum = S_COR(ast->connected.id.number.valid, ast->connected.id.number.str, l->lastnumberdialed);
+       } else {
+               ast_verb(1, "Error sending Callinfo to %s(%d) - No call direction in sub\n", d->name, l->instance);
+               return;
+       }
+       transmit_callinfo(subline->line->device, subline->line->instance, subline->callid, fromname, fromnum, toname, tonum, sub->calldirection);
 }
 
 static void transmit_connect(struct skinny_device *d, struct skinny_subchannel *sub)
@@ -2559,6 +2739,12 @@ static void transmit_callstate(struct skinny_device *d, int buttonInstance, unsi
 
        if (!(req = req_alloc(sizeof(struct call_state_message), CALL_STATE_MESSAGE)))
                return;
+       
+#ifdef SKINNY_DEVMODE
+       if (skinnydebug) {
+               ast_verb(3, "Transmitting CALL_STATE_MESSAGE to %s - line %d, callid %d, state %s\n", d->name, buttonInstance, callid, callstate2str(state));
+       }
+#endif
 
        req->data.callstate.callState = htolel(state);
        req->data.callstate.lineInstance = htolel(buttonInstance);
@@ -2703,8 +2889,8 @@ static void transmit_softkeysetres(struct skinny_device *d)
                return;
 
        req->data.softkeysets.softKeySetOffset = htolel(0);
-       req->data.softkeysets.softKeySetCount = htolel(11);
-       req->data.softkeysets.totalSoftKeySetCount = htolel(11);
+       req->data.softkeysets.softKeySetCount = htolel(13);
+       req->data.softkeysets.totalSoftKeySetCount = htolel(13);
        for (x = 0; x < sizeof(soft_key_default_definitions) / sizeof(struct soft_key_definitions); x++) {
                const uint8_t *defaults = softkeymode->defaults;
                /* XXX I wanted to get the size of the array dynamically, but that wasn't wanting to work.
@@ -2798,49 +2984,109 @@ static void transmit_capabilitiesreq(struct skinny_device *d)
 
 static int skinny_extensionstate_cb(const char *context, const char *exten, enum ast_extension_states state, void *data)
 {
-       struct skinny_speeddial *sd = data;
-       struct skinny_device *d = sd->parent;
+       struct skinny_container *container = data;
+       struct skinny_device *d = NULL;
        char hint[AST_MAX_EXTENSION];
 
-       if (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, sd->context, sd->exten)) {
-               /* If they are not registered, we will override notification and show no availability */
-               if (ast_device_state(hint) == AST_DEVICE_UNAVAILABLE) {
-                       transmit_lamp_indication(d, STIMULUS_LINE, sd->instance, SKINNY_LAMP_FLASH);
-                       transmit_callstate(d, sd->instance, 0, SKINNY_ONHOOK);
+       if (container->type == SKINNY_SDCONTAINER) {
+               struct skinny_speeddial *sd = container->data;
+               d = sd->parent;
+
+               if (skinnydebug) {
+                       ast_verb(2, "Got hint %s on speeddial %s\n", ast_extension_state2str(state), sd->label);
+               }
+
+               if (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, sd->context, sd->exten)) {
+                       /* If they are not registered, we will override notification and show no availability */
+                       if (ast_device_state(hint) == AST_DEVICE_UNAVAILABLE) {
+                               transmit_lamp_indication(d, STIMULUS_LINE, sd->instance, SKINNY_LAMP_FLASH);
+                               transmit_callstate(d, sd->instance, 0, SKINNY_ONHOOK);
+                               return 0;
+                       }
+                       switch (state) {
+                       case AST_EXTENSION_DEACTIVATED: /* Retry after a while */
+                       case AST_EXTENSION_REMOVED:     /* Extension is gone */
+                               ast_verb(2, "Extension state: Watcher for hint %s %s. Notify Device %s\n", exten, state == AST_EXTENSION_DEACTIVATED ? "deactivated" : "removed", d->name);
+                               sd->stateid = -1;
+                               transmit_lamp_indication(d, STIMULUS_LINE, sd->instance, SKINNY_LAMP_OFF);
+                               transmit_callstate(d, sd->instance, 0, SKINNY_ONHOOK);
+                               break;
+                       case AST_EXTENSION_RINGING:
+                       case AST_EXTENSION_UNAVAILABLE:
+                               transmit_lamp_indication(d, STIMULUS_LINE, sd->instance, SKINNY_LAMP_BLINK);
+                               transmit_callstate(d, sd->instance, 0, SKINNY_RINGIN);
+                               break;
+                       case AST_EXTENSION_BUSY: /* callstate = SKINNY_BUSY wasn't wanting to work - I'll settle for this */
+                       case AST_EXTENSION_INUSE:
+                               transmit_lamp_indication(d, STIMULUS_LINE, sd->instance, SKINNY_LAMP_ON);
+                               transmit_callstate(d, sd->instance, 0, SKINNY_CALLREMOTEMULTILINE);
+                               break;
+                       case AST_EXTENSION_ONHOLD:
+                               transmit_lamp_indication(d, STIMULUS_LINE, sd->instance, SKINNY_LAMP_WINK);
+                               transmit_callstate(d, sd->instance, 0, SKINNY_HOLD);
+                               break;
+                       case AST_EXTENSION_NOT_INUSE:
+                       default:
+                               transmit_lamp_indication(d, STIMULUS_LINE, sd->instance, SKINNY_LAMP_OFF);
+                               transmit_callstate(d, sd->instance, 0, SKINNY_ONHOOK);
+                               break;
+                       }
+               }
+               sd->laststate = state;
+       } else if (container->type == SKINNY_SUBLINECONTAINER) {
+               struct skinny_subline *subline = container->data;
+               struct skinny_line *l = subline->line;
+               d = l->device;
+
+               if (skinnydebug) {
+                       ast_verb(2, "Got hint %s on subline %s (%s@%s)\n", ast_extension_state2str(state), subline->name, exten, context);
+               }
+               
+               subline->extenstate = state;
+
+               if (subline->callid == 0) {
                        return 0;
                }
+
                switch (state) {
-               case AST_EXTENSION_DEACTIVATED: /* Retry after a while */
-               case AST_EXTENSION_REMOVED:     /* Extension is gone */
-                       ast_verb(2, "Extension state: Watcher for hint %s %s. Notify Device %s\n", exten, state == AST_EXTENSION_DEACTIVATED ? "deactivated" : "removed", d->name);
-                       sd->stateid = -1;
-                       transmit_lamp_indication(d, STIMULUS_LINE, sd->instance, SKINNY_LAMP_OFF);
-                       transmit_callstate(d, sd->instance, 0, SKINNY_ONHOOK);
+               case AST_EXTENSION_RINGING: /* Handled by normal ringin */
                        break;
-               case AST_EXTENSION_RINGING:
-               case AST_EXTENSION_UNAVAILABLE:
-                       transmit_lamp_indication(d, STIMULUS_LINE, sd->instance, SKINNY_LAMP_BLINK);
-                       transmit_callstate(d, sd->instance, 0, SKINNY_RINGIN);
-                       break;
-               case AST_EXTENSION_BUSY: /* callstate = SKINNY_BUSY wasn't wanting to work - I'll settle for this */
                case AST_EXTENSION_INUSE:
-                       transmit_lamp_indication(d, STIMULUS_LINE, sd->instance, SKINNY_LAMP_ON);
-                       transmit_callstate(d, sd->instance, 0, SKINNY_CALLREMOTEMULTILINE);
+                       if (subline->sub && (subline->sub->substate == SKINNY_CONNECTED)) { /* Device has a real call */
+                               transmit_callstate(d, l->instance, subline->callid, SKINNY_CONNECTED);
+                               transmit_selectsoftkeys(d, l->instance, subline->callid, KEYDEF_CONNECTED);
+                               transmit_displaypromptstatus(d, "Connected", 0, l->instance, subline->callid);
+                       } else { /* Some other device has active call */
+                               transmit_callstate(d, l->instance, subline->callid, SKINNY_CALLREMOTEMULTILINE);
+                               transmit_selectsoftkeys(d, l->instance, subline->callid, KEYDEF_SLACONNECTEDNOTACTIVE);
+                               transmit_displaypromptstatus(d, "In Use", 0, l->instance, subline->callid);
+                       }
+                       transmit_lamp_indication(d, STIMULUS_LINE, l->instance, SKINNY_LAMP_ON);
+                       transmit_ringer_mode(d, SKINNY_RING_OFF);
+                       transmit_activatecallplane(d, l);
                        break;
                case AST_EXTENSION_ONHOLD:
-                       transmit_lamp_indication(d, STIMULUS_LINE, sd->instance, SKINNY_LAMP_WINK);
-                       transmit_callstate(d, sd->instance, 0, SKINNY_HOLD);
+                       transmit_callstate(d, l->instance, subline->callid, SKINNY_HOLD);
+                       transmit_selectsoftkeys(d, l->instance, subline->callid, KEYDEF_SLAHOLD);
+                       transmit_displaypromptstatus(d, "Hold", 0, l->instance, subline->callid);
+                       transmit_lamp_indication(d, STIMULUS_LINE, l->instance, SKINNY_LAMP_BLINK);
+                       transmit_activatecallplane(d, l);
                        break;
                case AST_EXTENSION_NOT_INUSE:
-               default:
-                       transmit_lamp_indication(d, STIMULUS_LINE, sd->instance, SKINNY_LAMP_OFF);
-                       transmit_callstate(d, sd->instance, 0, SKINNY_ONHOOK);
+                       transmit_callstate(d, l->instance, subline->callid, SKINNY_ONHOOK);
+                       transmit_selectsoftkeys(d, l->instance, subline->callid, KEYDEF_ONHOOK);
+                       transmit_clearpromptmessage(d, l->instance, subline->callid);
+                       transmit_lamp_indication(d, STIMULUS_LINE, l->instance, SKINNY_LAMP_OFF);
+                       transmit_activatecallplane(d, l);
+                       subline->callid = 0;
                        break;
+               default:
+                       ast_log(LOG_WARNING, "AST_EXTENSION_STATE %s not configured\n", ast_extension_state2str(state));
                }
+       } else {
+               ast_log(LOG_WARNING, "Invalid data supplied to skinny_extensionstate_cb\n");
        }
 
-       sd->laststate = state;
-
        return 0;
 }
 
@@ -2860,7 +3106,7 @@ static void update_connectedline(struct skinny_subchannel *sub, const void *data
                ast_verb(3,"Sub %d - Updating\n", sub->callid);
        }
        
-       transmit_callinfo(sub);
+       send_callinfo(sub);
        if (sub->owner->_state == AST_STATE_UP) {
                transmit_callstate(d, l->instance, sub->callid, SKINNY_CONNECTED);
                transmit_displaypromptstatus(d, "Connected", 0, l->instance, sub->callid);
@@ -3657,6 +3903,7 @@ static char *_skinny_show_line(int type, int fd, struct mansession *s, const str
 {
        struct skinny_device *d;
        struct skinny_line *l;
+       struct skinny_subline *subline;
        struct ast_codec_pref *pref;
        int x = 0;
        char codec_buf[512];
@@ -3727,6 +3974,12 @@ static char *_skinny_show_line(int type, int fd, struct mansession *s, const str
                                ast_cli(fd, "Codec Order:      (");
                                print_codec_to_cli(fd, &l->prefs);
                                ast_cli(fd, ")\n");
+                               if  (AST_LIST_FIRST(&l->sublines)) {
+                                       ast_cli(fd, "Sublines:\n");
+                                       AST_LIST_TRAVERSE(&l->sublines, subline, list) {
+                                               ast_cli(fd, "     %s, %s@%s\n", subline->name, subline->exten, subline->context);
+                                       }
+                               }
                                ast_cli(fd, "\n");
                        } else { /* manager */
                                astman_append(s, "Channeltype: SKINNY\r\n");
@@ -3927,7 +4180,6 @@ static void *skinny_newcall(void *data)
        struct skinny_device *d = l->device;
        int res = 0;
 
-       ast_copy_string(l->lastnumberdialed, c->exten, sizeof(l->lastnumberdialed));
        ast_set_callerid(c,
                l->hidecallerid ? "" : l->cid_num,
                l->hidecallerid ? "" : l->cid_name,
@@ -3944,6 +4196,7 @@ static void *skinny_newcall(void *data)
        if (!sub->rtp) {
                start_rtp(sub);
        }
+       ast_verb(3, "Sub %d - Calling %s@%s\n", sub->callid, c->exten, c->context);
        res = ast_pbx_run(c);
        if (res) {
                ast_log(LOG_WARNING, "PBX exited non-zero\n");
@@ -4523,7 +4776,7 @@ static int skinny_indicate(struct ast_channel *ast, int ind, const void *data, s
        return 0;
 }
 
-static struct ast_channel *skinny_new(struct skinny_line *l, int state, const char *linkedid, int direction)
+static struct ast_channel *skinny_new(struct skinny_line *l, struct skinny_subline *subline, int state, const char *linkedid, int direction)
 {
        struct ast_channel *tmp;
        struct skinny_subchannel *sub;
@@ -4560,6 +4813,13 @@ static struct ast_channel *skinny_new(struct skinny_line *l, int state, const ch
                        sub->related = NULL;
                        sub->calldirection = direction;
 
+                       if (subline) {
+                               sub->subline = subline;
+                               subline->sub = sub;
+                       } else {
+                               sub->subline = NULL;
+                       }
+                       
                        AST_LIST_INSERT_HEAD(&l->sub, sub, list);
                        //l->activesub = sub;
                }
@@ -4610,7 +4870,11 @@ static struct ast_channel *skinny_new(struct skinny_line *l, int state, const ch
                        }
                }
 
-               ast_copy_string(tmp->context, l->context, sizeof(tmp->context));
+               if (subline) {
+                       ast_copy_string(tmp->context, subline->context, sizeof(tmp->context));
+               } else {
+                       ast_copy_string(tmp->context, l->context, sizeof(tmp->context));
+               }
                ast_copy_string(tmp->exten, l->exten, sizeof(tmp->exten));
 
                /* Don't use ast_set_callerid() here because it will
@@ -4679,6 +4943,7 @@ static char *substate2str(int ind) {
 static void setsubstate(struct skinny_subchannel *sub, int state)
 {
        struct skinny_line *l = sub->line;
+       struct skinny_subline *subline = sub->subline;
        struct skinny_device *d = l->device;
        struct ast_channel *c = sub->owner;
        pthread_t t;
@@ -4698,7 +4963,118 @@ static void setsubstate(struct skinny_subchannel *sub, int state)
        if ((state == SUBSTATE_RINGIN) && ((d->hookstate == SKINNY_OFFHOOK) || (AST_LIST_NEXT(AST_LIST_FIRST(&l->sub), list)))) {
                actualstate = SUBSTATE_CALLWAIT;
        }
-       
+
+       if ((state == SUBSTATE_CONNECTED) && (!subline) && (AST_LIST_FIRST(&l->sublines))) {
+               const char *slastation;
+               struct skinny_subline *tmpsubline;
+               slastation = pbx_builtin_getvar_helper(c, "SLASTATION");
+               ast_verb(3, "Connecting %s to subline\n", slastation);
+               if (slastation) {
+                       AST_LIST_TRAVERSE(&l->sublines, tmpsubline, list) {
+                               if (!strcasecmp(tmpsubline->stname, slastation)) {
+                                       subline = tmpsubline;
+                                       break;
+                               }
+                       }
+                       if (subline) {
+                               struct skinny_line *tmpline;
+                               subline->sub = sub;
+                               sub->subline = subline;
+                               subline->callid = sub->callid;
+                               send_callinfo(sub);
+                               AST_LIST_TRAVERSE(&lines, tmpline, all) {
+                                       AST_LIST_TRAVERSE(&tmpline->sublines, tmpsubline, list) {
+                                               if (!(subline == tmpsubline)) {
+                                                       if (!strcasecmp(subline->lnname, tmpsubline->lnname)) {
+                                                               tmpsubline->callid = callnums++;
+                                                               transmit_callstate(tmpsubline->line->device, tmpsubline->line->instance, tmpsubline->callid, SKINNY_OFFHOOK);
+                                                               push_callinfo(tmpsubline, sub);
+                                                               skinny_extensionstate_cb(NULL, NULL, tmpsubline->extenstate, tmpsubline->container);
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+
+       if (subline) { /* Different handling for subs under a subline, indications come through hints */
+               switch (actualstate) {
+               case SUBSTATE_ONHOOK:
+                       AST_LIST_REMOVE(&l->sub, sub, list);
+                       if (sub->related) {
+                               sub->related->related = NULL;
+                       }
+
+                       if (sub == l->activesub) {
+                               l->activesub = NULL;
+                               transmit_closereceivechannel(d, sub);
+                               transmit_stopmediatransmission(d, sub);
+                       }
+                       
+                       if (subline->callid) {
+                               transmit_stop_tone(d, l->instance, sub->callid);
+                               transmit_callstate(d, l->instance, subline->callid, SKINNY_CALLREMOTEMULTILINE);
+                               transmit_selectsoftkeys(d, l->instance, subline->callid, KEYDEF_SLACONNECTEDNOTACTIVE);
+                               transmit_displaypromptstatus(d, "In Use", 0, l->instance, subline->callid);
+                       }
+                       
+                       sub->cxmode = SKINNY_CX_RECVONLY;       
+                       sub->substate = SUBSTATE_ONHOOK;
+                       if (sub->rtp) {
+                               ast_rtp_instance_destroy(sub->rtp);
+                               sub->rtp = NULL;
+                       }
+                       sub->substate = SUBSTATE_ONHOOK;
+                       if (sub->owner) {
+                               ast_queue_hangup(sub->owner);
+                       }
+                       return;
+               case SUBSTATE_CONNECTED:
+                       transmit_activatecallplane(d, l);
+                       transmit_stop_tone(d, l->instance, sub->callid);
+                       transmit_selectsoftkeys(d, l->instance, subline->callid, KEYDEF_CONNECTED);
+                       transmit_callstate(d, l->instance, subline->callid, SKINNY_CONNECTED);
+                       if (!sub->rtp) {
+                               start_rtp(sub);
+                       }
+                       if (sub->substate == SUBSTATE_RINGIN || sub->substate == SUBSTATE_CALLWAIT) {
+                               ast_queue_control(sub->owner, AST_CONTROL_ANSWER);
+                       }
+                       if (sub->substate == SUBSTATE_DIALING || sub->substate == SUBSTATE_RINGOUT) {
+                               transmit_dialednumber(d, l->lastnumberdialed, l->instance, sub->callid);
+                       }
+                       if (sub->owner->_state != AST_STATE_UP) {
+                               ast_setstate(sub->owner, AST_STATE_UP);
+                       }
+                       sub->substate = SUBSTATE_CONNECTED;
+                       l->activesub = sub;
+                       return; 
+               case SUBSTATE_HOLD:
+                       if (sub->substate != SUBSTATE_CONNECTED) {
+                               ast_log(LOG_WARNING, "Cannot set substate to SUBSTATE_HOLD from %s (on call-%d)\n", substate2str(sub->substate), sub->callid);
+                               return;
+                       }
+                       transmit_activatecallplane(d, l);
+                       transmit_closereceivechannel(d, sub);
+                       transmit_stopmediatransmission(d, sub);
+
+                       transmit_callstate(d, l->instance, subline->callid, SKINNY_CALLREMOTEMULTILINE);
+                       transmit_selectsoftkeys(d, l->instance, subline->callid, KEYDEF_SLACONNECTEDNOTACTIVE);
+                       transmit_displaypromptstatus(d, "In Use", 0, l->instance, subline->callid);
+                       
+                       sub->substate = SUBSTATE_HOLD;
+
+                       ast_queue_control_data(sub->owner, AST_CONTROL_HOLD,
+                               S_OR(l->mohsuggest, NULL),
+                               !ast_strlen_zero(l->mohsuggest) ? strlen(l->mohsuggest) + 1 : 0);
+
+                       return;
+               default:
+                       ast_log(LOG_WARNING, "Substate handling under subline for state %d not implemented on Sub-%d\n", state, sub->callid);
+               }
+       }
+
        if ((d->hookstate == SKINNY_ONHOOK) && ((actualstate == SUBSTATE_OFFHOOK) || (actualstate == SUBSTATE_DIALING)
                || (actualstate == SUBSTATE_RINGOUT) || (actualstate == SUBSTATE_CONNECTED) || (actualstate == SUBSTATE_BUSY)
                || (actualstate == SUBSTATE_CONGESTION) || (actualstate == SUBSTATE_PROGRESS))) {
@@ -4711,7 +5087,7 @@ static void setsubstate(struct skinny_subchannel *sub, int state)
        }
 
        if (actualstate == sub->substate) {
-               transmit_callinfo(sub);
+               send_callinfo(sub);
                transmit_callstate(d, l->instance, sub->callid, SKINNY_HOLD);
                return;
        }
@@ -4769,24 +5145,40 @@ static void setsubstate(struct skinny_subchannel *sub, int state)
                break;
        case SUBSTATE_DIALING:
                if (ast_strlen_zero(sub->exten) || !ast_exists_extension(c, c->context, sub->exten, 1, l->cid_num)) {
-                       ast_log(LOG_WARNING, "Exten (%s) does not exist, unable to set substate DIALING on sub %d\n", sub->exten, sub->callid);
+                       ast_log(LOG_WARNING, "Exten (%s)@(%s) does not exist, unable to set substate DIALING on sub %d\n", sub->exten, c->context, sub->callid);
                        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_stop_tone(d, l->instance, sub->callid);
-               transmit_clear_display_message(d, l->instance, sub->callid);
-               transmit_selectsoftkeys(d, l->instance, sub->callid, KEYDEF_RINGOUT);
-               transmit_displaypromptstatus(d, "Dialing", 0, l->instance, sub->callid);
 
-               ast_copy_string(c->exten, sub->exten, sizeof(c->exten));
-               ast_copy_string(l->lastnumberdialed, sub->exten, sizeof(l->lastnumberdialed));
+               if (!sub->subline) {
+                       transmit_callstate(d, l->instance, sub->callid, SKINNY_OFFHOOK);
+                       transmit_stop_tone(d, l->instance, sub->callid);
+                       transmit_clear_display_message(d, l->instance, sub->callid);
+                       transmit_selectsoftkeys(d, l->instance, sub->callid, KEYDEF_RINGOUT);
+                       transmit_displaypromptstatus(d, "Dialing", 0, l->instance, sub->callid);
+               }
 
+               if  (AST_LIST_FIRST(&l->sublines)) {
+                       if (subline) {
+                               ast_copy_string(c->exten, subline->exten, sizeof(c->exten));
+                               ast_copy_string(c->context, "sla_stations", sizeof(c->context));
+                       } else {
+                               pbx_builtin_setvar_helper(c, "_DESTEXTEN", sub->exten);
+                               pbx_builtin_setvar_helper(c, "_DESTCONTEXT", c->context);
+                               ast_copy_string(c->exten, l->dialoutexten, sizeof(c->exten));
+                               ast_copy_string(c->context, l->dialoutcontext, sizeof(c->context));
+                               ast_copy_string(l->lastnumberdialed, sub->exten, sizeof(l->lastnumberdialed));
+                       }
+               } else {
+                       ast_copy_string(c->exten, sub->exten, sizeof(c->exten));
+                       ast_copy_string(l->lastnumberdialed, sub->exten, sizeof(l->lastnumberdialed));
+               }
+               
                sub->substate = SUBSTATE_DIALING;
        
                if (ast_pthread_create(&t, NULL, skinny_newcall, c)) {
@@ -4806,14 +5198,14 @@ static void setsubstate(struct skinny_subchannel *sub, int state)
                transmit_callstate(d, l->instance, sub->callid, SKINNY_RINGOUT);
                transmit_dialednumber(d, l->lastnumberdialed, l->instance, sub->callid);
                transmit_displaypromptstatus(d, "Ring Out", 0, l->instance, sub->callid);
-               transmit_callinfo(sub);
+               send_callinfo(sub);
                sub->substate = SUBSTATE_RINGOUT;
                break;
        case SUBSTATE_RINGIN:
                transmit_callstate(d, l->instance, sub->callid, SKINNY_RINGIN);
                transmit_selectsoftkeys(d, l->instance, sub->callid, KEYDEF_RINGIN);
                transmit_displaypromptstatus(d, "Ring-In", 0, l->instance, sub->callid);
-               transmit_callinfo(sub);
+               send_callinfo(sub);
                transmit_lamp_indication(d, STIMULUS_LINE, l->instance, SKINNY_LAMP_BLINK);
                transmit_ringer_mode(d, SKINNY_RING_INSIDE);
                transmit_activatecallplane(d, l);
@@ -4833,7 +5225,7 @@ static void setsubstate(struct skinny_subchannel *sub, int state)
                transmit_callstate(d, l->instance, sub->callid, SKINNY_CALLWAIT);
                transmit_selectsoftkeys(d, l->instance, sub->callid, KEYDEF_RINGIN);
                transmit_displaypromptstatus(d, "Callwaiting", 0, l->instance, sub->callid);
-               transmit_callinfo(sub);
+               send_callinfo(sub);
                transmit_lamp_indication(d, STIMULUS_LINE, l->instance, SKINNY_LAMP_BLINK);
                transmit_start_tone(d, SKINNY_CALLWAITTONE, l->instance, sub->callid);
        
@@ -4849,7 +5241,7 @@ static void setsubstate(struct skinny_subchannel *sub, int state)
                transmit_ringer_mode(d, SKINNY_RING_OFF);
                transmit_activatecallplane(d, l);
                transmit_stop_tone(d, l->instance, sub->callid);
-               transmit_callinfo(sub);
+               send_callinfo(sub);
                transmit_callstate(d, l->instance, sub->callid, SKINNY_CONNECTED);
                transmit_displaypromptstatus(d, "Connected", 0, l->instance, sub->callid);
                transmit_selectsoftkeys(d, l->instance, sub->callid, KEYDEF_CONNECTED);
@@ -4883,7 +5275,7 @@ static void setsubstate(struct skinny_subchannel *sub, int state)
                if (!d->earlyrtp) {
                        transmit_start_tone(d, SKINNY_BUSYTONE, l->instance, sub->callid);
                }
-               transmit_callinfo(sub);
+               send_callinfo(sub);
                transmit_callstate(d, l->instance, sub->callid, SKINNY_BUSY);
                transmit_displaypromptstatus(d, "Busy", 0, l->instance, sub->callid);
                sub->substate = SUBSTATE_BUSY;
@@ -4897,7 +5289,7 @@ static void setsubstate(struct skinny_subchannel *sub, int state)
                if (!d->earlyrtp) {
                        transmit_start_tone(d, SKINNY_REORDER, l->instance, sub->callid);
                }
-               transmit_callinfo(sub);
+               send_callinfo(sub);
                transmit_callstate(d, l->instance, sub->callid, SKINNY_CONGESTION);
                transmit_displaypromptstatus(d, "Congestion", 0, l->instance, sub->callid);
                sub->substate = SUBSTATE_CONGESTION;
@@ -4911,7 +5303,7 @@ static void setsubstate(struct skinny_subchannel *sub, int state)
                if (!d->earlyrtp) {
                        transmit_start_tone(d, SKINNY_ALERT, l->instance, sub->callid);
                }
-               transmit_callinfo(sub);
+               send_callinfo(sub);
                transmit_callstate(d, l->instance, sub->callid, SKINNY_PROGRESS);
                transmit_displaypromptstatus(d, "Call Progress", 0, l->instance, sub->callid);
                sub->substate = SUBSTATE_PROGRESS;
@@ -5063,7 +5455,7 @@ static int handle_transfer_button(struct skinny_subchannel *sub)
                if (!(sub->substate == SUBSTATE_HOLD)) {
                        setsubstate(sub, SUBSTATE_HOLD);
                }
-               c = skinny_new(l, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
+               c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
                if (c) {
                        newsub = c->tech_pvt;
                        /* point the sub and newsub at each other so we know they are related */
@@ -5251,7 +5643,7 @@ static int handle_stimulus_message(struct skinny_req *req, struct skinnysession
                        break;
                }
 
-               c = skinny_new(l, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
+               c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
                if (!c) {
                        ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
                } else {
@@ -5271,7 +5663,7 @@ static int handle_stimulus_message(struct skinny_req *req, struct skinnysession
                }
 
                if (!sub || !sub->owner)
-                       c = skinny_new(l, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
+                       c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
                else
                        c = sub->owner;
 
@@ -5307,7 +5699,7 @@ static int handle_stimulus_message(struct skinny_req *req, struct skinnysession
                }
 
                if (!sub || !sub->owner) {
-                       c = skinny_new(l, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
+                       c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
                } else {
                        c = sub->owner;
                }
@@ -5369,7 +5761,7 @@ static int handle_stimulus_message(struct skinny_req *req, struct skinnysession
                        ast_verb(1, "Received Stimulus: Forward All(%d/%d)\n", instance, callreference);
 
                if (!sub || !sub->owner) {
-                       c = skinny_new(l, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
+                       c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
                } else {
                        c = sub->owner;
                }
@@ -5386,7 +5778,7 @@ static int handle_stimulus_message(struct skinny_req *req, struct skinnysession
                        ast_verb(1, "Received Stimulus: Forward Busy (%d/%d)\n", instance, callreference);
 
                if (!sub || !sub->owner) {
-                       c = skinny_new(l, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
+                       c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
                } else {
                        c = sub->owner;
                }
@@ -5404,7 +5796,7 @@ static int handle_stimulus_message(struct skinny_req *req, struct skinnysession
 
 #if 0 /* Not sure how to handle this yet */
                if (!sub || !sub->owner) {
-                       c = skinny_new(l, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
+                       c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
                } else {
                        c = sub->owner;
                }
@@ -5447,7 +5839,7 @@ static int handle_stimulus_message(struct skinny_req *req, struct skinnysession
                        if (sub && sub->owner) {
                                ast_debug(1, "Current subchannel [%s] already has owner\n", sub->owner->name);
                        } else {
-                               c = skinny_new(l, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
+                               c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
                                if (c) {
                                        setsubstate(c->tech_pvt, SUBSTATE_OFFHOOK);
                                } else {
@@ -5520,7 +5912,7 @@ static int handle_offhook_message(struct skinny_req *req, struct skinnysession *
                if (sub && sub->owner) {
                        ast_debug(1, "Current sub [%s] already has owner\n", sub->owner->name);
                } else {
-                       c = skinny_new(l, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
+                       c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
                        if (c) {
                                setsubstate(c->tech_pvt, SUBSTATE_OFFHOOK);
                        } else {
@@ -5854,7 +6246,7 @@ static int handle_enbloc_call_message(struct skinny_req *req, struct skinnysessi
                l = sub->line;
        }
 
-       c = skinny_new(l, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
+       c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
 
        if(!c) {
                ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
@@ -5917,7 +6309,7 @@ static int handle_soft_key_event_message(struct skinny_req *req, struct skinnyse
                }
 
                if (!sub || !sub->owner) {
-                       c = skinny_new(l, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
+                       c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
                } else {
                        c = sub->owner;
                }
@@ -5934,9 +6326,9 @@ static int handle_soft_key_event_message(struct skinny_req *req, struct skinnyse
                        ast_verb(1, "Received Softkey Event: New Call(%d/%d)\n", instance, callreference);
 
                /* New Call ALWAYS gets a new sub-channel */
-               c = skinny_new(l, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
+               c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
                sub = c->tech_pvt;
-       
+
                if (!c) {
                        ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
                } else {
@@ -5946,7 +6338,16 @@ static int handle_soft_key_event_message(struct skinny_req *req, struct skinnyse
        case SOFTKEY_HOLD:
                if (skinnydebug)
                        ast_verb(1, "Received Softkey Event: Hold(%d/%d)\n", instance, callreference);
-               setsubstate(sub, SUBSTATE_HOLD);        
+               
+               if (sub) {
+                       setsubstate(sub, SUBSTATE_HOLD);        
+               } else { /* No sub, maybe an SLA call */
+                       struct skinny_subline *subline;
+                       if ((subline = find_subline_by_callid(d, callreference))) {
+                               setsubstate(subline->sub, SUBSTATE_HOLD);       
+                       }
+               }
+
                break;
        case SOFTKEY_TRNSFER:
                if (skinnydebug)
@@ -5979,7 +6380,7 @@ static int handle_soft_key_event_message(struct skinny_req *req, struct skinnyse
                        ast_verb(1, "Received Softkey Event: Forward All(%d/%d)\n", instance, callreference);
 
                if (!sub || !sub->owner) {
-                       c = skinny_new(l, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
+                       c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
                } else {
                        c = sub->owner;
                }
@@ -5997,7 +6398,7 @@ static int handle_soft_key_event_message(struct skinny_req *req, struct skinnyse
                        ast_verb(1, "Received Softkey Event: Forward Busy (%d/%d)\n", instance, callreference);
 
                if (!sub || !sub->owner) {
-                       c = skinny_new(l, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
+                       c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
                } else {
                        c = sub->owner;
                }
@@ -6016,7 +6417,7 @@ static int handle_soft_key_event_message(struct skinny_req *req, struct skinnyse
 
 #if 0 /* Not sure how to handle this yet */
                if (!sub || !sub->owner) {
-                       c = skinny_new(l, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
+                       c = skinny_new(l, NULL, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
                } else {
                        c = sub->owner;
                }
@@ -6045,7 +6446,7 @@ static int handle_soft_key_event_message(struct skinny_req *req, struct skinnyse
                        return 0;
                }
 
-               if (l->transfer && sub->xferor && sub->owner->_state >= AST_STATE_RING) {
+               if (l->transfer && sub && sub->xferor && sub->owner->_state >= AST_STATE_RING) {
                        /* We're allowed to transfer, we have two active calls and
                            we made at least one of the calls.  Let's try and transfer */
                        handle_transfer_button(sub);
@@ -6054,7 +6455,14 @@ static int handle_soft_key_event_message(struct skinny_req *req, struct skinnyse
        
                ast_devstate_changed(AST_DEVICE_NOT_INUSE, "Skinny/%s", l->name);
        
-               dumpsub(sub, 1);
+               if (sub) {
+                       dumpsub(sub, 1);
+               } else { /* No sub, maybe an SLA call */
+                       struct skinny_subline *subline;
+                       if ((subline = find_subline_by_callid(d, callreference))) {
+                               dumpsub(subline->sub, 1);
+                       }
+               }
 
                d->hookstate = SKINNY_ONHOOK;
        
@@ -6065,8 +6473,20 @@ static int handle_soft_key_event_message(struct skinny_req *req, struct skinnyse
        case SOFTKEY_RESUME:
                if (skinnydebug)
                        ast_verb(1, "Received Softkey Event: Resume(%d/%d)\n", instance, callreference);
-
-               activatesub(sub, SUBSTATE_CONNECTED);
+               
+               if (sub) {
+                       activatesub(sub, SUBSTATE_CONNECTED);
+               } else { /* No sub, maybe an inactive SLA call */
+                       struct skinny_subline *subline;
+                       subline = find_subline_by_callid(d, callreference);
+                       c = skinny_new(l, subline, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
+                       if (!c) {
+                               ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
+                       } else {
+                               sub = c->tech_pvt;
+                               dialandactivatesub(sub, subline->exten);
+                       }
+               }
                break;
        case SOFTKEY_ANSWER:
                if (skinnydebug)
@@ -6120,6 +6540,18 @@ static int handle_soft_key_event_message(struct skinny_req *req, struct skinnyse
        case SOFTKEY_JOIN:
                if (skinnydebug)
                        ast_verb(1, "Received Softkey Event: Join(%d/%d)\n", instance, callreference);
+               /* this is SLA territory, should not get here unless there is a meetme at subline */
+               {
+                       struct skinny_subline *subline;
+                       subline = find_subline_by_callid(d, callreference);
+                       c = skinny_new(l, subline, AST_STATE_DOWN, NULL, SKINNY_OUTGOING);
+                       if (!c) {
+                               ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", l->name, d->name);
+                       } else {
+                               sub = c->tech_pvt;
+                               dialandactivatesub(sub, subline->exten);
+                       }
+               }
                break;
        case SOFTKEY_MEETME:
                /* XXX How is this different from CONFRN? */
@@ -6578,6 +7010,7 @@ static int skinny_devicestate(void *data)
 static struct ast_channel *skinny_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, void *data, int *cause)
 {
        struct skinny_line *l;
+       struct skinny_subline *subline = NULL;
        struct ast_channel *tmpc = NULL;
        char tmp[256];
        char *dest = data;
@@ -6594,13 +7027,24 @@ static struct ast_channel *skinny_request(const char *type, struct ast_format_ca
        }
        l = find_line_by_name(tmp);
        if (!l) {
-               ast_log(LOG_NOTICE, "No available lines on: %s\n", dest);
-               return NULL;
+               subline = find_subline_by_name(tmp);
+               if (!subline) {
+                       ast_log(LOG_NOTICE, "No available lines on: %s\n", dest);
+                       return NULL;
+               }
+               l = subline->line;
        }
        ast_verb(3, "skinny_request(%s)\n", tmp);
-       tmpc = skinny_new(l, AST_STATE_DOWN, requestor ? requestor->linkedid : NULL, SKINNY_INCOMING);
+       tmpc = skinny_new(l, subline, AST_STATE_DOWN, requestor ? requestor->linkedid : NULL, SKINNY_INCOMING);
        if (!tmpc) {
                ast_log(LOG_WARNING, "Unable to make channel for '%s'\n", tmp);
+       } else if (subline) {
+               struct skinny_subchannel *sub = tmpc->tech_pvt;
+               subline->sub = sub;
+               subline->calldirection = SKINNY_INCOMING;
+               subline->substate = SUBSTATE_UNSET;
+               subline->callid = sub->callid;
+               sub->subline = subline;
        }
        return tmpc;
 }
@@ -6950,36 +7394,92 @@ static struct ast_channel *skinny_request(const char *type, struct ast_format_ca
                                }
                                continue;
                        }
+               } else if (!strcasecmp(v->name, "subline")) {
+                       if (type & (TYPE_LINE)) {
+                               struct skinny_subline *subline;
+                               struct skinny_container *container;
+                               char buf[256];
+                               char *stringp = buf, *exten, *stname, *context;
+
+                               if (!(subline = ast_calloc(1, sizeof(*subline)))) {
+                                       ast_log(LOG_WARNING, "Unable to allocate memory for subline %s. Ignoring subline.\n", v->value);
+                                       continue;
+                               }
+                               if (!(container = ast_calloc(1, sizeof(*container)))) {
+                                       ast_log(LOG_WARNING, "Unable to allocate memory for subline %s container. Ignoring subline.\n", v->value);
+                                       ast_free(subline);
+                                       continue;
+                               }
+                               
+                               ast_copy_string(buf, v->value, sizeof(buf));
+                               exten = strsep(&stringp, "@");
+                               ast_copy_string(subline->exten, ast_strip(exten), sizeof(subline->exten));
+                               stname = strsep(&exten, "_");
+                               ast_copy_string(subline->stname, ast_strip(stname), sizeof(subline->stname));
+                               ast_copy_string(subline->lnname, ast_strip(exten), sizeof(subline->lnname));
+                               context = strsep(&stringp, ",");
+                               ast_copy_string(subline->name, ast_strip(stringp), sizeof(subline->name));
+                               ast_copy_string(subline->context, ast_strip(context), sizeof(subline->context));
+
+                               subline->line = CLINE;
+                               subline->sub = NULL;
+
+                               container->type = SKINNY_SUBLINECONTAINER;
+                               container->data = subline;
+                               subline->container = container;
+                               AST_LIST_INSERT_HEAD(&CLINE->sublines, subline, list);
+                               continue;
+                       }
+               } else if (!strcasecmp(v->name, "dialoutcontext")) {
+                       if (type & (TYPE_LINE)) {
+                               ast_copy_string(CLINE_OPTS->dialoutcontext, v->value, sizeof(CLINE_OPTS->dialoutcontext));
+                               continue;
+                       }
+               } else if (!strcasecmp(v->name, "dialoutexten")) {
+                       if (type & (TYPE_LINE)) {
+                               ast_copy_string(CLINE_OPTS->dialoutexten, v->value, sizeof(CLINE_OPTS->dialoutexten));
+                               continue;
+                       }
                } else if (!strcasecmp(v->name, "speeddial")) {
                        if (type & (TYPE_DEVICE)) {
                                struct skinny_speeddial *sd;
+                               struct skinny_container *container;
+                               char buf[256];
+                               char *stringp = buf, *exten, *context, *label;
+
                                if (!(sd = ast_calloc(1, sizeof(*sd)))) {
                                        ast_log(LOG_WARNING, "Unable to allocate memory for speeddial %s. Ignoring speeddial.\n", v->name);
                                        continue;
-                               } else {
-                                       char buf[256];
-                                       char *stringp = buf, *exten, *context, *label;
-                                               ast_copy_string(buf, v->value, sizeof(buf));
-                                       exten = strsep(&stringp, ",");
-                                       if ((context = strchr(exten, '@'))) {
-                                               *context++ = '\0';
-                                       }
-                                       label = stringp;
-                                       ast_mutex_init(&sd->lock);
-                                       ast_copy_string(sd->exten, exten, sizeof(sd->exten));
-                                       if (!ast_strlen_zero(context)) {
-                                               sd->isHint = 1;
-                                               sd->instance = lineInstance++;
-                                               ast_copy_string(sd->context, context, sizeof(sd->context));
-                                       } else {
-                                               sd->isHint = 0;
-                                               sd->instance = speeddialInstance++;
-                                               sd->context[0] = '\0';
-                                       }
-                                       ast_copy_string(sd->label, S_OR(label, exten), sizeof(sd->label));
-                                       sd->parent = CDEV;
-                                       AST_LIST_INSERT_HEAD(&CDEV->speeddials, sd, list);
                                }
+                               if (!(container = ast_calloc(1, sizeof(*container)))) {
+                                       ast_log(LOG_WARNING, "Unable to allocate memory for speeddial %s container. Ignoring speeddial.\n", v->name);
+                                       ast_free(sd);
+                                       continue;
+                               }
+
+                               ast_copy_string(buf, v->value, sizeof(buf));
+                               exten = strsep(&stringp, ",");
+                               if ((context = strchr(exten, '@'))) {
+                                       *context++ = '\0';
+                               }
+                               label = stringp;
+                               ast_mutex_init(&sd->lock);
+                               ast_copy_string(sd->exten, exten, sizeof(sd->exten));
+                               if (!ast_strlen_zero(context)) {
+                                       sd->isHint = 1;
+                                       sd->instance = lineInstance++;
+                                       ast_copy_string(sd->context, context, sizeof(sd->context));
+                               } else {
+                                       sd->isHint = 0;
+                                       sd->instance = speeddialInstance++;
+                                       sd->context[0] = '\0';
+                               }
+                               ast_copy_string(sd->label, S_OR(label, exten), sizeof(sd->label));
+                               sd->parent = CDEV;
+                               container->type = SKINNY_SDCONTAINER;
+                               container->data = sd;
+                               sd->container = container;
+                               AST_LIST_INSERT_HEAD(&CDEV->speeddials, sd, list);
                                continue;
                        }
                } else if (!strcasecmp(v->name, "addon")) {
@@ -7008,6 +7508,7 @@ static struct ast_channel *skinny_request(const char *type, struct ast_format_ca
  {
        struct skinny_line *l, *temp;
        int update = 0;
+       struct skinny_container *container;
  
        ast_log(LOG_NOTICE, "Configuring skinny line %s.\n", lname);
 
@@ -7026,7 +7527,17 @@ static struct ast_channel *skinny_request(const char *type, struct ast_format_ca
                AST_LIST_UNLOCK(&lines);
                return NULL;
        }
+       if (!(container = ast_calloc(1, sizeof(*container)))) {
+               ast_log(LOG_WARNING, "Unable to allocate memory for line %s container.\n", lname);
+               skinny_line_destroy(l);
+               AST_LIST_UNLOCK(&lines);
+               return NULL;
+       }
 
+       container->type = SKINNY_LINECONTAINER;
+       container->data = l;
+       l->container = container;
+       
        memcpy(l, default_line, sizeof(*default_line));
        ast_mutex_init(&l->lock);
        ast_copy_string(l->name, lname, sizeof(l->name));
@@ -7284,6 +7795,7 @@ static void delete_devices(void)
                }
                /* Delete all speeddials for this device */
                while ((sd = AST_LIST_REMOVE_HEAD(&d->speeddials, list))) {
+                       free(sd->container);
                        free(sd);
                }
                /* Delete all addons for this device */