don't die if skinny cannot figure out hostname
[asterisk/asterisk.git] / channels / chan_skinny.c
index 1d59a59..e097499 100755 (executable)
@@ -9,6 +9,7 @@
  *
  * This program is free software, distributed under the terms of
  * the GNU General Public License
+ *
  */
 
 
@@ -477,7 +478,6 @@ struct hostent *hp;
 static int skinnysock  = -1;
 static pthread_t tcp_thread;
 static pthread_t accept_t;
-static ast_mutex_t devicelock = AST_MUTEX_INITIALIZER;
 static char context[AST_MAX_EXTENSION] = "default";
 static char language[MAX_LANGUAGE] = "";
 static char musicclass[MAX_LANGUAGE] = "";
@@ -527,6 +527,17 @@ static int callnums = 1;
 #define SKINNY_REORDER 37
 #define SKINNY_CALLWAITTONE 45
 
+#define SKINNY_LAMP_OFF 1
+#define SKINNY_LAMP_ON  2
+#define SKINNY_LAMP_WINK 3
+#define SKINNY_LAMP_FLASH 4
+#define SKINNY_LAMP_BLINK 5
+
+#define SKINNY_RING_OFF 1
+#define SKINNY_RING_INSIDE 2
+#define SKINNY_RING_OUTSIDE 3
+#define SKINNY_RING_FEATURE 4
+
 #define TYPE_TRUNK             1
 #define TYPE_LINE              2
 
@@ -549,6 +560,7 @@ static int callnums = 1;
 #define SKINNY_CX_MUTE     4
 #define SKINNY_CX_INACTIVE 4
 
+#if 0
 static char *skinny_cxmodes[] = {
     "sendonly",
     "recvonly",
@@ -556,6 +568,7 @@ static char *skinny_cxmodes[] = {
     "confrnce",
     "inactive"
 };
+#endif
 
 /* driver scheduler */
 static struct sched_context *sched;
@@ -568,9 +581,12 @@ static ast_mutex_t usecnt_lock = AST_MUTEX_INITIALIZER;
 /* Protect the monitoring thread, so only one process can kill or start it, and not
    when it's doing something critical. */
 static ast_mutex_t monlock        = AST_MUTEX_INITIALIZER;
-
+/* Protect the network socket */
 static ast_mutex_t netlock        = AST_MUTEX_INITIALIZER;
+/* Protect the session list */
 static ast_mutex_t sessionlock = AST_MUTEX_INITIALIZER;
+/* Protect the device list */
+static ast_mutex_t devicelock = AST_MUTEX_INITIALIZER;
 
 /* This is the thread for the monitor which checks for input on the channels
    which are not currently in use.  */
@@ -676,7 +692,7 @@ static skinny_req *req_alloc(size_t size)
        return req;
 }
 
-static struct skinny_subchannel *find_subchannel(struct skinny_line *l)
+static struct skinny_subchannel *find_subchannel_by_line(struct skinny_line *l)
 {
        /* Need to figure out how to determine which sub we want */
        
@@ -684,6 +700,48 @@ static struct skinny_subchannel *find_subchannel(struct skinny_line *l)
        return sub;
 }
 
+static struct skinny_subchannel *find_subchannel_by_name(char *dest)
+{
+       struct skinny_line *l;
+       struct skinny_device *d;
+       char line[256];
+       char *at;
+       char *device;
+       
+       strncpy(line, dest, sizeof(line) - 1);
+       at = strchr(line, '@');
+       if (!at) {
+               ast_log(LOG_NOTICE, "Device '%s' has no @ (at) sign!\n", dest);
+               return NULL;
+       }
+       *at = '\0';
+       at++;
+       device = at;
+       ast_mutex_lock(&devicelock);
+       d = devices;
+       while(d) {
+               if (!strcasecmp(d->name, device)) {
+                       if (skinnydebug) {
+                               printf("Found device: %s\n", d->name);
+                       }
+                       /* Found the device */
+                       l = d->lines;
+                       while (l) {
+                               /* Search for the right line */
+                               if (!strcasecmp(l->name, line)) {
+                                       ast_mutex_unlock(&devicelock);
+                                       return l->sub;
+                               }
+                               l = l->next;
+                       }
+               }
+               d = d->next;
+       }
+       /* Device not found*/
+       ast_mutex_unlock(&devicelock);
+       return NULL;
+}
+
 static int transmit_response(struct skinnysession *s, skinny_req *req)
 {
        int res = 0;
@@ -710,7 +768,7 @@ static void transmit_speaker_mode(struct skinnysession *s, int mode)
 {
        skinny_req *req;
 
-       req = req_alloc(sizeof(struct set_ringer_message));
+       req = req_alloc(sizeof(struct set_speaker_message));
        if (!req) {
                ast_log(LOG_ERROR, "Unable to allocate skinny_request, this is bad\n");
                return;
@@ -835,7 +893,7 @@ static void transmit_lamp_indication(struct skinnysession *s, int instance, int
        }       
        req->len = sizeof(set_lamp_message)+4;
        req->e = SET_LAMP_MESSAGE;
-       req->data.setlamp.stimulus = 9;  // magic number
+       req->data.setlamp.stimulus = 0x9;  // magic number
        req->data.setlamp.stimulusInstance = instance;
        req->data.setlamp.deviceStimulus = indication;
        transmit_response(s, req);
@@ -856,9 +914,6 @@ static void transmit_ringer_mode(struct skinnysession *s, int mode)
        transmit_response(s, req);
 }
 
-
-
-
 /* I do not believe skinny can deal with video. 
    Anyone know differently? */
 static struct ast_rtp *skinny_get_vrtp_peer(struct ast_channel *chan)
@@ -921,7 +976,7 @@ static int skinny_show_lines(int fd, int argc, char *argv[])
        d = devices;
        while(d) {
                l = d->lines;
-               ast_cli(fd, "Device '%s' at %s\n", d->name, d->addr.sin_addr.s_addr);
+               ast_cli(fd, "Device '%s' at %s\n", d->name, inet_ntoa(d->addr.sin_addr));
                while(l) {
                        ast_cli(fd, "   -- '%s@%s in '%s' is %s\n", l->name, d->name, l->context, l->sub->owner ? "active" : "idle");
                        haslines = 1;
@@ -1366,58 +1421,82 @@ static void *skinny_ss(void *data)
 
 static int skinny_call(struct ast_channel *ast, char *dest, int timeout)
 {
-       int res;
+       int res = 0;
+       int tone = 0;
        struct skinny_line *l;
     struct skinny_subchannel *sub;
+       struct skinnysession *session;
+       
+       sub = ast->pvt->pvt;
+    l = sub->parent;
+       session = l->parent->session;
+
+       if (!l->parent->registered) {
+               ast_log(LOG_ERROR, "Device not registered, cannot call %s\n", dest);
+               return -1;
+       }
+       
+       if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
+               ast_log(LOG_WARNING, "skinny_call called on %s, neither down nor reserved\n", ast->name);
+               return -1;
+       }
 
     if (skinnydebug) {
         ast_verbose(VERBOSE_PREFIX_3 "skinny_call(%s)\n", ast->name);
     }
-       
-       sub = ast->pvt->pvt;
-    l = sub->parent;
 
-    switch (l->hookstate) {
+       if (l->dnd) {
+               ast_queue_control(ast, AST_CONTROL_BUSY, 0);
+               return -1;
+       }
+   
+       switch (l->hookstate) {
         case SKINNY_OFFHOOK:
-            // call waiting
+            tone = SKINNY_CALLWAITTONE;
             break;
         case SKINNY_ONHOOK:
+                       tone = SKINNY_ALERT;
+                       break;
         default:
-            // ring
+            ast_log(LOG_ERROR, "Don't know how to deal with hookstate %d\n", l->hookstate);
             break;
     }
 
-       if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
-               ast_log(LOG_WARNING, "skinny_call called on %s, neither down nor reserved\n", ast->name);
-               return -1;
-       }
+       transmit_lamp_indication(session, l->instance, SKINNY_LAMP_BLINK);
+       transmit_ringer_mode(session, SKINNY_RING_INSIDE);
+       transmit_tone(session, tone);
+       transmit_callstate(session, l->instance, SKINNY_RINGIN, sub->callid);
+
+// Set the prompt
+// Select the active softkeys
+
+       ast_setstate(ast, AST_STATE_RINGING);
+       ast_queue_control(ast, AST_CONTROL_RINGING, 0);
 
-       res = 0;
        sub->outgoing = 1;
-    sub->cxmode = SKINNY_CX_RECVONLY;
+//    sub->cxmode = SKINNY_CX_RECVONLY;
        if (l->type == TYPE_LINE) {
         if (!sub->rtp) {
             start_rtp(sub);
         } else {
+                       /* do we need to anything if there already is an RTP allocated? */
 //           transmit_modify_request(sub);
         }
 
+#if 0
         if (sub->next->owner && sub->next->callid) {
             /* try to prevent a callwait from disturbing the other connection */
             sub->next->cxmode = SKINNY_CX_RECVONLY;
       //      transmit_modify_request(sub->next);
         }
-
-//             transmit_notify_request_with_callerid(sub, tone, ast->callerid);
-               ast_setstate(ast, AST_STATE_RINGING);
-               ast_queue_control(ast, AST_CONTROL_RINGING, 0);
-
+               
                /* not sure what this doing */
                if (sub->next->owner && sub->next->callid) {
             /* Put the connection back in sendrecv */
             sub->next->cxmode = SKINNY_CX_SENDRECV;
 //           transmit_modify_request(sub->next);
         }
+#endif
 
        } else {
                ast_log(LOG_NOTICE, "Don't know how to dial on trunks yet\n");
@@ -1440,10 +1519,18 @@ static int skinny_hangup(struct ast_channel *ast)
         ast_log(LOG_DEBUG, "Asked to hangup channel not connected\n");
         return 0;
     }
-    if ((sub->parent->type = TYPE_LINE) && (sub->parent->hookstate == SKINNY_OFFHOOK)) {
-               sub->parent->hookstate = SKINNY_ONHOOK;
-               transmit_callstate(s, l->instance, SKINNY_ONHOOK, sub->callid);
-               transmit_speaker_mode(s, SKINNY_SPEAKEROFF); 
+
+       if (l->parent->registered) {
+                   if ((sub->parent->type = TYPE_LINE) && (sub->parent->hookstate == SKINNY_OFFHOOK)) {
+                       sub->parent->hookstate = SKINNY_ONHOOK;
+                       transmit_callstate(s, l->instance, SKINNY_ONHOOK, sub->callid);
+                       transmit_speaker_mode(s, SKINNY_SPEAKEROFF); 
+               } else if ((sub->parent->type = TYPE_LINE) && (sub->parent->hookstate == SKINNY_ONHOOK)) {
+                       transmit_callstate(s, l->instance, SKINNY_ONHOOK, sub->callid);
+                       transmit_speaker_mode(s, SKINNY_SPEAKEROFF); 
+                       transmit_ringer_mode(s, SKINNY_RING_OFF);
+                       transmit_tone(s, SKINNY_SILENCE);
+               } 
        }
     ast_mutex_lock(&sub->lock);
     sub->owner = NULL;
@@ -1552,11 +1639,10 @@ static int skinny_fixup(struct ast_channel *oldchan, struct ast_channel *newchan
 
 static int skinny_senddigit(struct ast_channel *ast, char digit)
 {
-//     struct skinny_subchannel *sub = ast->pvt->pvt;
-       char tmp[2];
-       tmp[0] = digit;
-       tmp[1] = '\0';
-//     transmit_notify_request(sub, tmp);
+       struct skinny_subchannel *sub = ast->pvt->pvt;
+//     int tmp;
+//     sprintf(tmp, "%d", digit);  // not right
+//     transmit_tone(sub->parent->parent->session, digit);
        return -1;
 }
 
@@ -1716,7 +1802,13 @@ static int handle_message(skinny_req *req, struct skinnysession *s)
        time_t timer;
        struct tm *cmtime;
        pthread_t t;
-               
+       
+       if ( (!s->device) && (req->e != REGISTER_MESSAGE && req->e != ALARM_MESSAGE)) {
+               ast_log(LOG_WARNING, "Client sent message #%d without first registering.\n", req->e);
+               free(req);
+               return 0;
+       }
+
                
        switch(req->e)  {
        case ALARM_MESSAGE:
@@ -1801,7 +1893,7 @@ static int handle_message(skinny_req *req, struct skinnysession *s)
                        if (skinnydebug)
                                printf("Recieved Stimulus: Line\n");
 
-                       sub = find_subchannel(s->device->lines);
+                       sub = find_subchannel_by_line(s->device->lines);
                        transmit_speaker_mode(s, 1);  // Turn on
                break;
                default:
@@ -1918,7 +2010,7 @@ static int handle_message(skinny_req *req, struct skinnysession *s)
                memset(req, 0, SKINNY_MAX_PACKET);
                req->len = sizeof(line_stat_res_message)+4;
                req->e = LINE_STAT_RES_MESSAGE; 
-               sub = find_subchannel(s->device->lines);
+               sub = find_subchannel_by_line(s->device->lines);
                if (!sub) {
                        ast_log(LOG_NOTICE, "No available lines on: %s\n", s->device->name);
                        return 0;
@@ -1948,38 +2040,44 @@ static int handle_message(skinny_req *req, struct skinnysession *s)
                transmit_response(s, req);
                break;
        case OFFHOOK_MESSAGE:
-               transmit_ringer_mode(s,1); // Ring off
-               transmit_lamp_indication(s, s->device->lines->instance, 2); // Lamp on
+               transmit_ringer_mode(s,SKINNY_RING_OFF);
+               transmit_lamp_indication(s, s->device->lines->instance, SKINNY_LAMP_ON); 
                
-               sub = find_subchannel(s->device->lines);
+               sub = find_subchannel_by_line(s->device->lines);
                if (!sub) {
                        ast_log(LOG_NOTICE, "No available lines on: %s\n", s->device->name);
                        return 0;
                }
                sub->parent->hookstate = SKINNY_OFFHOOK;
-               if (!sub->owner) {      
+               
                        if (sub->outgoing) {
-                               // deal with asterisk skinny outbound calls     
-                       } else {        
                                transmit_callstate(s, s->device->lines->instance, SKINNY_OFFHOOK, sub->callid);
-                               transmit_tone(s, SKINNY_DIALTONE);
-                               c = skinny_new(sub, AST_STATE_DOWN);                    
-                               if (c) {
-                                       /* start switch */
-                                       if (pthread_create(&t, NULL, skinny_ss, c)) {
-                           ast_log(LOG_WARNING, "Unable to create switch thread: %s\n", strerror(errno));
-                                               ast_hangup(c);
+                               transmit_tone(s, SKINNY_SILENCE);
+                               ast_setstate(sub->owner, AST_STATE_UP);
+                               // select soft keys
+                       } else {        
+                               if (!sub->owner) {      
+
+                                       transmit_callstate(s, s->device->lines->instance, SKINNY_OFFHOOK, sub->callid);
+                                       transmit_tone(s, SKINNY_DIALTONE);
+                                       c = skinny_new(sub, AST_STATE_DOWN);                    
+                                       if(c) {
+                                               /* start switch */
+                                               if (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", sub->parent->name, s->device->name);
                                        }
+                               
                                } else {
-                          ast_log(LOG_WARNING, "Unable to create channel for %s@%s\n", sub->parent->name, s->device->name);
+                                       ast_log(LOG_DEBUG, "Current sub [%s] already has owner\n", sub->owner->name);
                                }
                        }
-               } else {
-                       ast_log(LOG_DEBUG, "Current sub [%s] already has owner\n", sub->owner->name);
-               }
                break;
        case ONHOOK_MESSAGE:
-               sub = find_subchannel(s->device->lines);
+               sub = find_subchannel_by_line(s->device->lines);
                if (sub->parent->hookstate == SKINNY_ONHOOK) {
                        /* Somthing else already put us back on hook */ 
                        break;
@@ -2019,9 +2117,9 @@ static int handle_message(skinny_req *req, struct skinnysession *s)
        }
        if ((sub->parent->hookstate == SKINNY_ONHOOK) && (!sub->rtp) && (!sub->next->rtp)) {
                        if (has_voicemail(sub->parent)) {
-                               transmit_lamp_indication(s, s->device->lines->instance, 4); // Flash
+                               transmit_lamp_indication(s, s->device->lines->instance, SKINNY_LAMP_FLASH); 
             } else {
-                               transmit_lamp_indication(s, s->device->lines->instance, 1); // Off
+                               transmit_lamp_indication(s, s->device->lines->instance, SKINNY_LAMP_OFF);
             }
        }
           break;
@@ -2040,7 +2138,8 @@ static int handle_message(skinny_req *req, struct skinnysession *s)
                }
                f.subclass  = d;  
                f.src = "skinny";
-               sub = find_subchannel(s->device->lines);                
+
+               sub = find_subchannel_by_line(s->device->lines);                
 
                if (sub->owner) {
                        /* XXX MUST queue this frame to all subs in threeway call if threeway call is active */
@@ -2066,7 +2165,7 @@ static int handle_message(skinny_req *req, struct skinnysession *s)
                memcpy(&sin.sin_addr, addr, sizeof(sin.sin_addr));  // Endian?
                sin.sin_port = htons(port);
        
-               sub = find_subchannel(s->device->lines);
+               sub = find_subchannel_by_line(s->device->lines);
                ast_rtp_set_peer(sub->rtp, &sin);
                ast_rtp_get_us(sub->rtp, &us);
                
@@ -2135,21 +2234,22 @@ static int get_input(struct skinnysession *s)
  
        res = ast_select(s->fd + 1, &fds, NULL, NULL, NULL);  
  
-       if (res < 0) {  
-               ast_log(LOG_WARNING, "Select returned error: %s\n", strerror(errno));  
-       } else if (res > 0) {  
-               ast_mutex_lock(&s->lock);  
+       if (res < 0) {
+               ast_log(LOG_WARNING, "Select returned error: %s\n", strerror(errno));
+       } else if (res > 0) {
                memset(s->inbuf,0,sizeof(s->inbuf));
-               res = read(s->fd, s->inbuf, 4);  
-               dlen = *(int *)s->inbuf; 
-               if (res < 1) {  
-                       return -1;  
-               }  
-               res = read(s->fd, s->inbuf+4, dlen+4);  
-               ast_mutex_unlock(&s->lock);  
-               if (res < 1) {  
-                       return -1;  
-               }  
+               res = read(s->fd, s->inbuf, 4);
+               if (res != 4) {
+                       ast_log(LOG_WARNING, "Skinny Client sent less data than expected.\n");
+                       return -1;
+               }
+               dlen = *(int *)s->inbuf;
+               res = read(s->fd, s->inbuf+4, dlen+4);
+               ast_mutex_unlock(&s->lock);
+               if (res != (dlen+4)) {
+                       ast_log(LOG_WARNING, "Skinny Client sent less data than expected.\n");
+                       return -1;
+               } 
  
        }  
        return res;  
@@ -2169,6 +2269,7 @@ static skinny_req *skinny_req_parse(struct skinnysession *s)
        memcpy(req, s->inbuf, *(int*)(s->inbuf)+8); // +8
        if (req->e < 0) {
                ast_log(LOG_ERROR, "Event Message is NULL from socket %d, This is bad\n", s->fd);
+               free(req);
                return NULL;
        }
        return req;
@@ -2322,7 +2423,6 @@ static struct ast_channel *skinny_request(char *type, int format, void *data)
 {
        int oldformat;
        struct skinny_subchannel *sub;
-       struct skinny_device *d = NULL;
        struct ast_channel *tmpc = NULL;
        char tmp[256];
        char *dest = data;
@@ -2333,16 +2433,16 @@ static struct ast_channel *skinny_request(char *type, int format, void *data)
                ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%d'\n", format);
                return NULL;
        }
-       strncpy(tmp, dest, sizeof(tmp) - 1);  // XXX FIX
+       
+       strncpy(tmp, dest, sizeof(tmp) - 1);
        if (!strlen(tmp)) {
-               ast_log(LOG_NOTICE, "Skinny channels require something!?\n");
+               ast_log(LOG_NOTICE, "Skinny channels require a device\n");
                return NULL;
        }
        
-       sub = find_subchannel(d->lines);  
-
+       sub = find_subchannel_by_name(tmp);  
        if (!sub) {
-               ast_log(LOG_NOTICE, "No available lines on: %s\n", d->name);
+               ast_log(LOG_NOTICE, "No available lines on: %s\n", dest);
                return NULL;
        }
        
@@ -2352,19 +2452,6 @@ static struct ast_channel *skinny_request(char *type, int format, void *data)
                     sub->parent->callwaiting, sub->parent->dnd, sub->owner ? 1 : 0, sub->next->owner ? 1: 0);
     }
        
-       /* Must be busy */
-       if (((sub->parent->callwaiting) && ((sub->owner) && (sub->next->owner))) ||
-        ((!sub->parent->callwaiting) && (sub->owner)) ||
-         (sub->parent->dnd && (!strlen(sub->parent->call_forward)))) {
-         if (sub->parent->hookstate == SKINNY_ONHOOK) {
-             if (has_voicemail(sub->parent)) {
-//                 transmit_notify_request(sub,"vmwi(+)");
-             } else {
-//                 transmit_notify_request(sub,"vmwi(-)");
-             }
-         }
-               return NULL;
-    }
        tmpc = skinny_new(sub->owner ? sub->next : sub, AST_STATE_DOWN);
        if (!tmpc)
                ast_log(LOG_WARNING, "Unable to make channel for '%s'\n", tmp);
@@ -2374,7 +2461,7 @@ static struct ast_channel *skinny_request(char *type, int format, void *data)
        return tmpc;
 }
 
-static int reload_config()
+static int reload_config(void)
 {
        int on=1;
        struct ast_config *cfg;
@@ -2473,8 +2560,8 @@ static int reload_config()
        }
        if (skinnysock < 0) {
                skinnysock = socket(AF_INET, SOCK_STREAM, 0);
-               if(!setsockopt(skinnysock,SOL_SOCKET,SO_REUSEADDR,&on,0)) {
-                       ast_log(LOG_ERROR, "Set Socket Options failed: %s", strerror(errno));
+               if(setsockopt(skinnysock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
+                       ast_log(LOG_ERROR, "Set Socket Options failed: errno %d, %s", errno, strerror(errno));
                        return 0;
                }
 
@@ -2516,6 +2603,49 @@ static int reload_config()
        return 0;
 }
 
+void delete_devices(void)
+{
+       struct skinny_device *d, *dlast;
+       struct skinny_line *l, *llast;
+       struct skinny_subchannel *sub, *slast;
+       
+       ast_mutex_lock(&devicelock);
+       
+       /* Delete all devices */
+       for (d=devices;d;) {
+               
+               /* Delete all lines for this device */
+               for (l=d->lines;l;) {
+                       /* Delete all subchannels for this line */
+                       for (sub=l->sub;sub;) {
+                               slast = sub;
+                               sub = sub->next;
+                               free(slast);
+                       }
+                       llast = l;
+                       l = l->next;
+                       free(llast);
+               }
+               dlast = d;
+               d = d->next;
+               free(dlast);
+       }
+       devices=NULL;
+       ast_mutex_unlock(&devicelock);
+}
+
+int reload(void)
+{
+#if 0
+// XXX Causes Seg
+
+       delete_devices();
+       reload_config();
+       restart_monitor();
+#endif
+       return 0;
+}
+
 
 int load_module()
 {