Fix potential segfault, add support for MacOS X locks
[asterisk/asterisk.git] / manager.c
index ea73858..c77fec1 100755 (executable)
--- a/manager.c
+++ b/manager.c
@@ -25,6 +25,7 @@
 #include <signal.h>
 #include <errno.h>
 #include <unistd.h>
+#include <sys/poll.h>
 #include <asterisk/channel.h>
 #include <asterisk/file.h>
 #include <asterisk/manager.h>
 #include <asterisk/md5.h>
 #include <asterisk/acl.h>
 
+struct fast_originate_helper
+{
+       char tech[256];
+       char data[256];
+       int timeout;
+       char app[256];
+       char appdata[256];
+       char callerid[256];
+       char variable[256];
+       char account[256];
+       char context[256];
+       char exten[256];
+       int priority;
+};
+
 static int enabled = 0;
 static int portno = DEFAULT_MANAGER_PORT;
 static int asock = -1;
@@ -63,16 +79,12 @@ static struct mansession *sessions = NULL;
 static struct manager_action *first_action = NULL;
 static ast_mutex_t actionlock = AST_MUTEX_INITIALIZER;
 
-
-
-
 int ast_carefulwrite(int fd, char *s, int len, int timeoutms) 
 {
        /* Try to write string, but wait no more than ms milliseconds
           before timing out */
        int res=0;
-       struct timeval tv;
-       fd_set fds;
+       struct pollfd fds[1];
        while(len) {
                res = write(fd, s, len);
                if ((res < 0) && (errno != EAGAIN)) {
@@ -81,19 +93,16 @@ int ast_carefulwrite(int fd, char *s, int len, int timeoutms)
                if (res < 0) res = 0;
                len -= res;
                s += res;
-               tv.tv_sec = timeoutms / 1000;
-               tv.tv_usec = timeoutms % 1000;
-               FD_ZERO(&fds);
-               FD_SET(fd, &fds);
+               fds[0].fd = fd;
+               fds[0].events = POLLOUT;
                /* Wait until writable again */
-               res = select(fd + 1, NULL, &fds, NULL, &tv);
+               res = poll(fds, 1, timeoutms);
                if (res < 1)
                        return -1;
        }
        return res;
 }
 
-
 static int handle_showmancmds(int fd, int argc, char *argv[])
 {
        struct manager_action *cur = first_action;
@@ -231,6 +240,24 @@ static int get_perm(char *instr)
        return ret;
 }
 
+static int set_eventmask(struct mansession *s, char *eventmask)
+{
+       if (!eventmask)
+               return -1;
+       if (!strcasecmp(eventmask, "on") || ast_true(eventmask)) {
+               ast_mutex_lock(&s->lock);
+               s->send_events = 1;
+               ast_mutex_unlock(&s->lock);
+               return 1;
+       } else if (!strcasecmp(eventmask, "off") || ast_false(eventmask)) {
+               ast_mutex_lock(&s->lock);
+               s->send_events = 0;
+               ast_mutex_unlock(&s->lock);
+               return 0;
+       }
+       return -1;
+}
+
 static int authenticate(struct mansession *s, struct message *m)
 {
        struct ast_config *cfg;
@@ -240,7 +267,6 @@ static int authenticate(struct mansession *s, struct message *m)
        char *authtype = astman_get_header(m, "AuthType");
        char *key = astman_get_header(m, "Key");
        char *events = astman_get_header(m, "Events");
-       int send_events = events ? ast_true(events) : 1;
        
        cfg = ast_load("manager.conf");
        if (!cfg)
@@ -306,7 +332,8 @@ static int authenticate(struct mansession *s, struct message *m)
                s->readperm = get_perm(ast_variable_retrieve(cfg, cat, "read"));
                s->writeperm = get_perm(ast_variable_retrieve(cfg, cat, "write"));
                ast_destroy(cfg);
-               s->send_events=send_events;
+               if (events)
+                       set_eventmask(s, events);
                return 0;
        }
        ast_log(LOG_NOTICE, "%s tried to authenticate with non-existant user '%s'\n", inet_ntoa(s->sin.sin_addr), user);
@@ -320,35 +347,21 @@ static int action_ping(struct mansession *s, struct message *m)
        return 0;
 }
 
-
-static int events_on_off(struct mansession *s,int onoff) {
-       ast_mutex_lock(&s->lock);
-       s->send_events = onoff ? 1 : 0;
-       ast_mutex_unlock(&s->lock);
-       return s->send_events;
-}
-
-
 static int action_events(struct mansession *s, struct message *m)
 {
        char *mask = astman_get_header(m, "EventMask");
-       char reply[25];
        int res;
-       int true=0;
-       
-       /* ast_true might wanna learn to include 'on' as a true stmt  */
-       if(!strcasecmp(mask,"on"))
-               true = 1;
-       else 
-               true = ast_true(mask);
-       
-       res = events_on_off(s,true);
-       sprintf(reply,"Events are now %s",res ? "on" : "off");
-       astman_send_response(s, m,reply, NULL);
+
+       res = set_eventmask(s, mask);
+       if (res > 0)
+               astman_send_response(s, m, "Events On", NULL);
+       else if (res == 0)
+               astman_send_response(s, m, "Events Off", NULL);
+       else
+               astman_send_response(s, m, "EventMask parse error", NULL);
        return 0;
 }
 
-
 static int action_logoff(struct mansession *s, struct message *m)
 {
        astman_send_response(s, m, "Goodbye", "Thanks for all the fish.");
@@ -363,18 +376,20 @@ static int action_hangup(struct mansession *s, struct message *m)
                astman_send_error(s, m, "No channel specified");
                return 0;
        }
-       c = ast_channel_walk(NULL);
+       c = ast_channel_walk_locked(NULL);
        while(c) {
                if (!strcasecmp(c->name, name)) {
                        break;
                }
-               c = ast_channel_walk(c);
+               ast_mutex_unlock(&c->lock);
+               c = ast_channel_walk_locked(c);
        }
        if (!c) {
                astman_send_error(s, m, "No such channel");
                return 0;
        }
        ast_softhangup(c, AST_SOFTHANGUP_EXPLICIT);
+       ast_mutex_unlock(&c->lock);
        astman_send_ack(s, m, "Channel Hungup");
        return 0;
 }
@@ -386,7 +401,7 @@ static int action_status(struct mansession *s, struct message *m)
        struct ast_channel *c;
        char bridge[256];
        astman_send_ack(s, m, "Channel status will follow");
-       c = ast_channel_walk(NULL);
+       c = ast_channel_walk_locked(NULL);
         if (id && strlen(id))
                 snprintf(idText,256,"ActionID: %s\r\n",id);
        while(c) {
@@ -423,7 +438,8 @@ static int action_status(struct mansession *s, struct message *m)
                        c->name, c->callerid ? c->callerid : "<unknown>", 
                        ast_state2str(c->_state), bridge, c->uniqueid, idText);
                }
-               c = ast_channel_walk(c);
+               ast_mutex_unlock(&c->lock);
+               c = ast_channel_walk_locked(c);
        }
        ast_cli(s->fd,
        "Event: StatusComplete\r\n"
@@ -439,6 +455,7 @@ static int action_redirect(struct mansession *s, struct message *m)
        char *exten = astman_get_header(m, "Exten");
        char *context = astman_get_header(m, "Context");
        char *priority = astman_get_header(m, "Priority");
+       struct ast_channel *chan, *chan2 = NULL;
        int pi = 0;
        int res;
        if (!name || !strlen(name)) {
@@ -449,10 +466,20 @@ static int action_redirect(struct mansession *s, struct message *m)
                astman_send_error(s, m, "Invalid priority\n");
                return 0;
        }
-       res = ast_async_goto_by_name(name, context, exten, pi);
+       chan = ast_get_channel_by_name_locked(name);
+       if (!chan) {
+               astman_send_error(s, m, "Channel not existant");
+               return 0;
+       }
+       if (strlen(name2))
+               chan2 = ast_get_channel_by_name_locked(name2);
+       res = ast_async_goto(chan, context, exten, pi);
        if (!res) {
                if (strlen(name2)) {
-                       res = ast_async_goto_by_name(name2, context, exten, pi);
+                       if (chan2)
+                               res = ast_async_goto(chan2, context, exten, pi);
+                       else
+                               res = -1;
                        if (!res)
                                astman_send_ack(s, m, "Dual Redirect successful");
                        else
@@ -461,16 +488,23 @@ static int action_redirect(struct mansession *s, struct message *m)
                        astman_send_ack(s, m, "Redirect successful");
        } else
                astman_send_error(s, m, "Redirect failed");
+       if (chan)
+               ast_mutex_unlock(&chan->lock);
+       if (chan2)
+               ast_mutex_unlock(&chan2->lock);
        return 0;
 }
 
 static int action_command(struct mansession *s, struct message *m)
 {
        char *cmd = astman_get_header(m, "Command");
+       char *id = astman_get_header(m, "ActionID");
        ast_mutex_lock(&s->lock);
        s->blocking = 1;
        ast_mutex_unlock(&s->lock);
        ast_cli(s->fd, "Response: Follows\r\n");
+       if (id && strlen(id))
+               ast_cli(s->fd, "ActionID: %s\r\n", id);
        /* FIXME: Wedge a ActionID response in here, waiting for later changes */
        ast_cli_command(s->fd, cmd);
        ast_cli(s->fd, "--END COMMAND--\r\n\r\n");
@@ -480,6 +514,20 @@ static int action_command(struct mansession *s, struct message *m)
        return 0;
 }
 
+static void *fast_originate(void *data)
+{
+       struct fast_originate_helper *in = data;
+       int res;
+       int reason = 0;
+       if (strlen(in->app)) {
+               res = ast_pbx_outgoing_app(in->tech, AST_FORMAT_SLINEAR, in->data, in->timeout, in->app, in->appdata, &reason, 1, strlen(in->callerid) ? in->callerid : NULL, in->variable, in->account);
+       } else {
+               res = ast_pbx_outgoing_exten(in->tech, AST_FORMAT_SLINEAR, in->data, in->timeout, in->context, in->exten, in->priority, &reason, 1, strlen(in->callerid) ? in->callerid : NULL, in->variable, in->account);
+       }   
+       free(in);
+       return NULL;
+}
+
 static int action_originate(struct mansession *s, struct message *m)
 {
        char *name = astman_get_header(m, "Channel");
@@ -492,12 +540,15 @@ static int action_originate(struct mansession *s, struct message *m)
        char *account = astman_get_header(m, "Account");
        char *app = astman_get_header(m, "Application");
        char *appdata = astman_get_header(m, "Data");
+       char *async = astman_get_header(m, "Async");
        char *tech, *data;
        int pi = 0;
        int res;
        int to = 30000;
        int reason = 0;
        char tmp[256];
+       pthread_t th;
+       pthread_attr_t attr;
        if (!name) {
                astman_send_error(s, m, "Channel not specified");
                return 0;
@@ -519,10 +570,47 @@ static int action_originate(struct mansession *s, struct message *m)
        }
        *data = '\0';
        data++;
-       if (strlen(app)) {
+       if (ast_true(async))
+       {
+               struct fast_originate_helper *fast = malloc(sizeof(struct fast_originate_helper));
+               if (!fast)
+               {
+                       res = -1;
+               }
+               else
+               {
+                       memset(fast, 0, sizeof(struct fast_originate_helper));
+                       strncpy(fast->tech, tech, sizeof(fast->tech) - 1);
+                       strncpy(fast->data, data, sizeof(fast->data) - 1);
+                       strncpy(fast->app, app, sizeof(fast->app) - 1);
+                       strncpy(fast->appdata, appdata, sizeof(fast->appdata) - 1);
+                       strncpy(fast->callerid, callerid, sizeof(fast->callerid) - 1);
+                       strncpy(fast->variable, variable, sizeof(fast->variable) - 1);
+                       strncpy(fast->account, account, sizeof(fast->account) - 1);
+                       strncpy(fast->context, context, sizeof(fast->context) - 1);
+                       strncpy(fast->exten, exten, sizeof(fast->exten) - 1);
+                       fast->timeout = to;
+                       fast->priority = pi;
+                       pthread_attr_init(&attr);
+                       pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+                       if (pthread_create(&th, &attr, fast_originate, fast))
+                       {
+                               res = -1;
+                       }
+                       else
+                       {
+                               res = 0;
+                       }
+               }
+       } else if (strlen(app)) {
                res = ast_pbx_outgoing_app(tech, AST_FORMAT_SLINEAR, data, to, app, appdata, &reason, 0, strlen(callerid) ? callerid : NULL, variable, account);
        } else {
-               res = ast_pbx_outgoing_exten(tech, AST_FORMAT_SLINEAR, data, to, context, exten, pi, &reason, 0, strlen(callerid) ? callerid : NULL, variable, account);
+               if (exten && context && pi)
+                       res = ast_pbx_outgoing_exten(tech, AST_FORMAT_SLINEAR, data, to, context, exten, pi, &reason, 0, strlen(callerid) ? callerid : NULL, variable, account);
+               else {
+                       astman_send_error(s, m, "Originate with 'Exten' requires 'Context' and 'Priority'");
+                       return 0;
+               }
        }   
        if (!res)
                astman_send_ack(s, m, "Originate successfully queued");
@@ -618,18 +706,20 @@ static int action_timeout(struct mansession *s, struct message *m)
                astman_send_error(s, m, "No timeout specified");
                return 0;
        }
-       c = ast_channel_walk(NULL);
+       c = ast_channel_walk_locked(NULL);
        while(c) {
                if (!strcasecmp(c->name, name)) {
                        break;
                }
-               c = ast_channel_walk(c);
+               ast_mutex_unlock(&c->lock);
+               c = ast_channel_walk_locked(c);
        }
        if (!c) {
                astman_send_error(s, m, "No such channel");
                return 0;
        }
        ast_channel_setwhentohangup(c, timeout);
+       ast_mutex_unlock(&c->lock);
        astman_send_ack(s, m, "Timeout Set");
        return 0;
 }
@@ -710,7 +800,7 @@ static int get_input(struct mansession *s, char *output)
        /* output must have at least sizeof(s->inbuf) space */
        int res;
        int x;
-       fd_set fds;
+       struct pollfd fds[1];
        for (x=1;x<s->inlen;x++) {
                if ((s->inbuf[x] == '\n') && (s->inbuf[x-1] == '\r')) {
                        /* Copy output data up to and including \r\n */
@@ -727,9 +817,9 @@ static int get_input(struct mansession *s, char *output)
                ast_log(LOG_WARNING, "Dumping long line with no return from %s: %s\n", inet_ntoa(s->sin.sin_addr), s->inbuf);
                s->inlen = 0;
        }
-       FD_ZERO(&fds);
-       FD_SET(s->fd, &fds);
-       res = ast_select(s->fd + 1, &fds, NULL, NULL, NULL);
+       fds[0].fd = s->fd;
+       fds[0].events = POLLIN;
+       res = poll(fds, 1, -1);
        if (res < 0) {
                ast_log(LOG_WARNING, "Select returned error: %s\n", strerror(errno));
        } else if (res > 0) {
@@ -853,9 +943,7 @@ int manager_event(int category, char *event, char *fmt, ...)
                                va_start(ap, fmt);
                                vsnprintf(tmp, sizeof(tmp), fmt, ap);
                                va_end(ap);
-
                                ast_carefulwrite(s->fd,tmp,strlen(tmp),100);
-                               /*write(s->fd, tmp, strlen(tmp));*/
                                ast_cli(s->fd, "\r\n");
                        }
                        ast_mutex_unlock(&s->lock);
@@ -972,13 +1060,18 @@ int init_manager(void)
        val = ast_variable_retrieve(cfg, "general", "block-sockets");
        if(val)
                block_sockets = ast_true(val);
-       
 
-       if ((val = ast_variable_retrieve(cfg, "general", "portno"))) {
+       if ((val = ast_variable_retrieve(cfg, "general", "port"))) {
+               if (sscanf(val, "%d", &portno) != 1) {
+                       ast_log(LOG_WARNING, "Invalid port number '%s'\n", val);
+                       portno = DEFAULT_MANAGER_PORT;
+               }
+       } else if ((val = ast_variable_retrieve(cfg, "general", "portno"))) {
                if (sscanf(val, "%d", &portno) != 1) {
                        ast_log(LOG_WARNING, "Invalid port number '%s'\n", val);
                        portno = DEFAULT_MANAGER_PORT;
                }
+               ast_log(LOG_NOTICE, "Use of portno in manager.conf deprecated.  Please use 'port=%s' instead.\n", val);
        }
        
        ba.sin_family = AF_INET;