Try immediately logged in agents first, then callbacklogin ones
[asterisk/asterisk.git] / channels / chan_agent.c
index d9eef1c..466181c 100755 (executable)
@@ -33,6 +33,7 @@
 #include <asterisk/app.h>
 #include <asterisk/musiconhold.h>
 #include <asterisk/manager.h>
+#include <asterisk/parking.h>
 #include <sys/socket.h>
 #include <errno.h>
 #include <unistd.h>
@@ -48,15 +49,25 @@ static char *tdesc = "Call Agent Proxy Channel";
 static char *config = "agents.conf";
 
 static char *app = "AgentLogin";
+static char *app2 = "AgentCallbackLogin";
 
 static char *synopsis = "Call agent login";
+static char *synopsis2 = "Call agent callback login";
 
 static char *descrip =
-"  AgentLogin():\n"
+"  AgentLogin([AgentNo][|options]):\n"
 "Asks the agent to login to the system.  Always returns -1.  While\n"
 "logged in, the agent can receive calls and will hear a 'beep'\n"
 "when a new call comes in.  The agent can dump the call by pressing\n"
-"the star key.\n";
+"the star key.\n"
+"The option string may contain zero or more of the following characters:\n"
+"      's' -- silent login - do not announce the login ok segment\n";
+
+static char *descrip2 =
+"  AgentCallbackLogin([AgentNo][|@context]):\n"
+"Asks the agent to login to the system with callback.  Always returns -1.\n"
+"The agent's callback extension is called (optionally with the specified\n"
+"context. \n";
 
 static char moh[80] = "default";
 
@@ -64,6 +75,11 @@ static char moh[80] = "default";
 
 static int capability = -1;
 
+static unsigned int group;
+static int autologoff;
+static int wrapuptime;
+static int ackcall;
+
 static int usecnt =0;
 static pthread_mutex_t usecnt_lock = AST_MUTEX_INITIALIZER;
 
@@ -73,11 +89,24 @@ static pthread_mutex_t agentlock = AST_MUTEX_INITIALIZER;
 static struct agent_pvt {
        pthread_mutex_t lock;                           /* Channel private lock */
        int dead;                                                       /* Poised for destruction? */
+       int pending;                                            /* Not a real agent -- just pending a match */
+       int abouttograb;                                        /* About to grab */
+       int autologoff;                                 /* Auto timeout time */
+       int ackcall;                                    /* ackcall */
+       time_t start;                                           /* When call started */
+       struct timeval lastdisc;                        /* When last disconnected */
+       int wrapuptime;                                         /* Wrapup time in ms */
+       unsigned int group;                                     /* Group memberships */
+       int acknowledged;                                       /* Acknowledged */
        char moh[80];                                           /* Which music on hold */
        char agent[AST_MAX_AGENT];                      /* Agent ID */
        char password[AST_MAX_AGENT];           /* Password for Agent login */
        char name[AST_MAX_AGENT];
+       pthread_mutex_t app_lock;                       /* Synchronization between owning applications */
+       volatile pthread_t owning_app;          /* Owning application thread id */
+       volatile int app_sleep_cond;            /* Sleep condition for the login app */
        struct ast_channel *owner;                      /* Agent */
+       char loginchan[80];
        struct ast_channel *chan;                       /* Channel we use */
        struct agent_pvt *next;                         /* Agent */
 } *agents = NULL;
@@ -91,13 +120,31 @@ static struct agent_pvt {
 } while(0)
 
 
-static int add_agent(struct ast_variable *var)
+static void agent_unlink(struct agent_pvt *agent)
+{
+       struct agent_pvt *p, *prev;
+       prev = NULL;
+       p = agents;
+       while(p) {
+               if (p == agent) {
+                       if (prev)
+                               prev->next = agent->next;
+                       else
+                               agents = agent->next;
+                       break;
+               }
+               prev = p;
+               p = p->next;
+       }
+}
+
+static struct agent_pvt *add_agent(char *agent, int pending)
 {
        char tmp[256];
        char *password=NULL, *name=NULL;
-       struct agent_pvt *p;
+       struct agent_pvt *p, *prev;
        
-       strncpy(tmp, var->value, sizeof(tmp));
+       strncpy(tmp, agent, sizeof(tmp));
        if ((password = strchr(tmp, ','))) {
                *password = '\0';
                password++;
@@ -108,10 +155,12 @@ static int add_agent(struct ast_variable *var)
                name++;
                while (*name < 33) name++; 
        }
+       prev=NULL;
        p = agents;
        while(p) {
-               if (!strcmp(p->agent, tmp))
+               if (!pending && !strcmp(p->agent, tmp))
                        break;
+               prev = p;
                p = p->next;
        }
        if (!p) {
@@ -119,20 +168,52 @@ static int add_agent(struct ast_variable *var)
                if (p) {
                        memset(p, 0, sizeof(struct agent_pvt));
                        strncpy(p->agent, tmp, sizeof(p->agent) -1);
-                       p->next = agents;
-                       agents = p;
+                       ast_pthread_mutex_init( &p->lock );
+                       ast_pthread_mutex_init( &p->app_lock );
+                       p->owning_app = -1;
+                       p->app_sleep_cond = 1;
+                       p->group = group;
+                       p->pending = pending;
+                       p->next = NULL;
+                       if (prev)
+                               prev->next = p;
+                       else
+                               agents = p;
                        
                }
        }
        if (!p)
-               return -1;
+               return NULL;
        strncpy(p->password, password ? password : "", sizeof(p->password) - 1);
        strncpy(p->name, name ? name : "", sizeof(p->name) - 1);
        strncpy(p->moh, moh, sizeof(p->moh) - 1);
-       p->dead = 0;
+       p->ackcall = ackcall;
+       p->autologoff = autologoff;
+       p->wrapuptime = wrapuptime;
+       if (pending)
+               p->dead = 1;
+       else
+               p->dead = 0;
+       return p;
+}
+
+static int agent_cleanup(struct agent_pvt *p)
+{
+       struct ast_channel *chan = p->owner;
+       p->owner = NULL;
+       chan->pvt->pvt = NULL;
+       p->app_sleep_cond = 1;
+       /* Release ownership of the agent to other threads (presumably running the login app). */
+       ast_pthread_mutex_unlock(&p->app_lock);
+       if (chan)
+               ast_channel_free(chan);
+       if (p->dead)
+               free(p);
        return 0;
 }
 
+static int check_availability(struct agent_pvt *newlyavailable, int needlock);
+
 static int agent_answer(struct ast_channel *ast)
 {
        ast_log(LOG_WARNING, "Huh?  Agent is being asked to answer?\n");
@@ -143,13 +224,44 @@ static struct ast_frame  *agent_read(struct ast_channel *ast)
 {
        struct agent_pvt *p = ast->pvt->pvt;
        struct ast_frame *f = NULL;
+       static struct ast_frame null_frame = { AST_FRAME_NULL, };
+       static struct ast_frame answer_frame = { AST_FRAME_CONTROL, AST_CONTROL_ANSWER };
        ast_pthread_mutex_lock(&p->lock);
        if (p->chan)
                f = ast_read(p->chan);
+       else
+               f = &null_frame;
        if (!f) {
-               /* If there's a channel, make it NULL */
-               if (p->chan)
+               /* If there's a channel, hang it up  (if it's on a callback) make it NULL */
+               if (p->chan) {
+                       if (strlen(p->loginchan))
+                               ast_hangup(p->chan);
                        p->chan = NULL;
+                       p->acknowledged = 0;
+               }
+       }
+       if (f && (f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_ANSWER)) {
+/* TC */
+       if (p->ackcall) {
+               if (option_verbose > 2)
+                       ast_verbose(VERBOSE_PREFIX_3 "%s answered, waiting for '#' to acknowledge\n", p->chan->name);
+               /* Don't pass answer along */
+               ast_frfree(f);
+               f = &null_frame;
+        }
+        else {
+               p->acknowledged = 1;
+               f = &answer_frame;
+        }
+       }
+       if (f && (f->frametype == AST_FRAME_DTMF) && (f->subclass == '#')) {
+               if (!p->acknowledged) {
+                       if (option_verbose > 2)
+                               ast_verbose(VERBOSE_PREFIX_3 "%s acknowledged\n", p->chan->name);
+                       p->acknowledged = 1;
+                       ast_frfree(f);
+                       f = &answer_frame;
+               }
        }
        if (f && (f->frametype == AST_FRAME_DTMF) && (f->subclass == '*')) {
                /* * terminates call */
@@ -168,6 +280,8 @@ static int agent_write(struct ast_channel *ast, struct ast_frame *f)
        ast_pthread_mutex_lock(&p->lock);
        if (p->chan)
                res = ast_write(p->chan, f);
+       else
+               res = 0;
        CLEANUP(ast, p);
        ast_pthread_mutex_unlock(&p->lock);
        return res;
@@ -179,6 +293,7 @@ static int agent_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
        ast_pthread_mutex_lock(&p->lock);
        if (p->owner != oldchan) {
                ast_log(LOG_WARNING, "old channel wasn't %p but was %p\n", oldchan, p->owner);
+               ast_pthread_mutex_unlock(&p->lock);
                return -1;
        }
        p->owner = newchan;
@@ -193,6 +308,8 @@ static int agent_indicate(struct ast_channel *ast, int condition)
        ast_pthread_mutex_lock(&p->lock);
        if (p->chan)
                res = ast_indicate(p->chan, condition);
+       else
+               res = 0;
        ast_pthread_mutex_unlock(&p->lock);
        return res;
 }
@@ -204,6 +321,8 @@ static int agent_digit(struct ast_channel *ast, char digit)
        ast_pthread_mutex_lock(&p->lock);
        if (p->chan)
                res = p->chan->pvt->send_digit(p->chan, digit);
+       else
+               res = 0;
        ast_pthread_mutex_unlock(&p->lock);
        return res;
 }
@@ -213,21 +332,57 @@ static int agent_call(struct ast_channel *ast, char *dest, int timeout)
        struct agent_pvt *p = ast->pvt->pvt;
        int res = -1;
        ast_pthread_mutex_lock(&p->lock);
+       if (!p->chan) {
+               if (p->pending) {
+                       ast_log(LOG_DEBUG, "Pretending to dial on pending agent\n");
+                       ast_setstate(ast, AST_STATE_DIALING);
+                       res = 0;
+               } else {
+                       ast_log(LOG_NOTICE, "Whoa, they hung up between alloc and call...  what are the odds of that?\n");
+                       res = -1;
+               }
+               ast_pthread_mutex_unlock(&p->lock);
+               return res;
+       } else if (strlen(p->loginchan)) {
+               time(&p->start);
+               /* Call on this agent */
+               if (option_verbose > 2)
+                       ast_verbose(VERBOSE_PREFIX_3 "outgoing agentcall, to agent '%s', on '%s'\n", p->agent, p->chan->name);
+               res = ast_call(p->chan, p->loginchan, 0);
+               CLEANUP(ast,p);
+               ast_pthread_mutex_unlock(&p->lock);
+               return res;
+       }
+       ast_verbose( VERBOSE_PREFIX_3 "agent_call, call to agent '%s' call on '%s'\n", p->agent, p->chan->name);
+       ast_log( LOG_DEBUG, "Playing beep, lang '%s'\n", p->chan->language);
        res = ast_streamfile(p->chan, "beep", p->chan->language);
-       if (!res)
+       ast_log( LOG_DEBUG, "Played beep, result '%d'\n", res);
+       if (!res) {
                res = ast_waitstream(p->chan, "");
+               ast_log( LOG_DEBUG, "Waited for stream, result '%d'\n", res);
+       }
        if (!res) {
                res = ast_set_read_format(p->chan, ast_best_codec(p->chan->nativeformats));
+               ast_log( LOG_DEBUG, "Set read format, result '%d'\n", res);
                if (res)
                        ast_log(LOG_WARNING, "Unable to set read format to %d\n", ast_best_codec(p->chan->nativeformats));
+       } else {
+               // Agent hung-up
+               p->chan = NULL;
        }
+
        if (!res) {
                ast_set_write_format(p->chan, ast_best_codec(p->chan->nativeformats));
+               ast_log( LOG_DEBUG, "Set write format, result '%d'\n", res);
                if (res)
                        ast_log(LOG_WARNING, "Unable to set write format to %d\n", ast_best_codec(p->chan->nativeformats));
        }
-       /* Call is immediately up */
-       ast_setstate(ast, AST_STATE_UP);
+       if( !res )
+       {
+               /* Call is immediately up */
+               ast_setstate(ast, AST_STATE_UP);
+       }
+       CLEANUP(ast,p);
        ast_pthread_mutex_unlock(&p->lock);
        return res;
 }
@@ -235,38 +390,137 @@ static int agent_call(struct ast_channel *ast, char *dest, int timeout)
 static int agent_hangup(struct ast_channel *ast)
 {
        struct agent_pvt *p = ast->pvt->pvt;
+       int howlong = 0;
        ast_pthread_mutex_lock(&p->lock);
        p->owner = NULL;
        ast->pvt->pvt = NULL;
-       ast_pthread_mutex_unlock(&p->lock);
+       p->app_sleep_cond = 1;
+       if (p->start && (ast->_state != AST_STATE_UP))
+               howlong = time(NULL) - p->start;
+       time(&p->start);
        if (p->chan) {
                /* If they're dead, go ahead and hang up on the agent now */
-               if (p->dead)
+               if (strlen(p->loginchan)) {
+                       p->acknowledged = 0;
+                       if (p->chan) {
+                               /* Recognize the hangup and pass it along immediately */
+                               ast_hangup(p->chan);
+                               p->chan = NULL;
+                       }
+                       ast_log(LOG_DEBUG, "Hungup, howlong is %d, autologoff is %d\n", howlong, p->autologoff);
+                       if (howlong  && p->autologoff && (howlong > p->autologoff)) {
+                               ast_log(LOG_NOTICE, "Agent '%s' didn't answer/confirm within %d seconds (waited %d)\n", p->name, p->autologoff, howlong);
+                               strcpy(p->loginchan, "");
+                       }
+               } else if (p->dead) {
+                       ast_pthread_mutex_lock(&p->chan->lock);
                        ast_softhangup(p->chan, AST_SOFTHANGUP_EXPLICIT);
-               ast_moh_start(p->chan, p->moh);
-       } else if (p->dead) 
+                       ast_pthread_mutex_unlock(&p->chan->lock);
+               } else {
+                       ast_pthread_mutex_lock(&p->chan->lock);
+                       ast_moh_start(p->chan, p->moh);
+                       ast_pthread_mutex_unlock(&p->chan->lock);
+               }
+       }
+#if 0
+               ast_pthread_mutex_unlock(&p->lock);
+               /* Release ownership of the agent to other threads (presumably running the login app). */
+               ast_pthread_mutex_unlock(&p->app_lock);
+       } else if (p->dead) {
                /* Go ahead and lose it */
+               ast_pthread_mutex_unlock(&p->lock);
+               /* Release ownership of the agent to other threads (presumably running the login app). */
+               ast_pthread_mutex_unlock(&p->app_lock);
+       } else {
+               ast_pthread_mutex_unlock(&p->lock);
+               /* Release ownership of the agent to other threads (presumably running the login app). */
+               ast_pthread_mutex_unlock(&p->app_lock);
+       }
+#endif 
+       ast_pthread_mutex_unlock(&p->lock);
+       /* Release ownership of the agent to other threads (presumably running the login app). */
+       ast_pthread_mutex_unlock(&p->app_lock);
+
+       if (p->pending) {
+               ast_pthread_mutex_lock(&agentlock);
+               agent_unlink(p);
+               ast_pthread_mutex_unlock(&agentlock);
+       }
+       if (p->abouttograb) {
+               /* Let the "about to grab" thread know this isn't valid anymore, and let it
+                  kill it later */
+               p->abouttograb = 0;
+       } else if (p->dead) {
                free(p);
+       } else if (p->chan) {
+               /* Not dead -- check availability now */
+               ast_pthread_mutex_lock(&p->lock);
+               /* check_availability(p, 1); */
+               /* Store last disconnect time */
+               gettimeofday(&p->lastdisc, NULL);
+               ast_pthread_mutex_unlock(&p->lock);
+       }
        return 0;
 }
 
+static int agent_cont_sleep( void *data )
+{
+       struct agent_pvt *p;
+       struct timeval tv;
+       int res;
+
+       p = (struct agent_pvt *)data;
+
+       ast_pthread_mutex_lock(&p->lock);
+       res = p->app_sleep_cond;
+       if (p->lastdisc.tv_sec) {
+               gettimeofday(&tv, NULL);
+               if ((tv.tv_sec - p->lastdisc.tv_sec) * 1000 + 
+                       (tv.tv_usec - p->lastdisc.tv_usec) / 1000 > p->wrapuptime) 
+                       res = 1;
+       }
+       ast_pthread_mutex_unlock(&p->lock);
+#if 0
+       if( !res )
+               ast_log( LOG_DEBUG, "agent_cont_sleep() returning %d\n", res );
+#endif         
+       return res;
+}
+
 static struct ast_channel *agent_new(struct agent_pvt *p, int state)
 {
        struct ast_channel *tmp;
+       struct ast_frame null_frame = { AST_FRAME_NULL };
+#if 0
        if (!p->chan) {
                ast_log(LOG_WARNING, "No channel? :(\n");
                return NULL;
        }
+#endif 
        tmp = ast_channel_alloc(0);
        if (tmp) {
-               tmp->nativeformats = p->chan->nativeformats;
-               snprintf(tmp->name, sizeof(tmp->name), "Agent/%s", p->agent);
+               if (p->chan) {
+                       tmp->nativeformats = p->chan->nativeformats;
+                       tmp->writeformat = p->chan->writeformat;
+                       tmp->pvt->rawwriteformat = p->chan->writeformat;
+                       tmp->readformat = p->chan->readformat;
+                       tmp->pvt->rawreadformat = p->chan->readformat;
+                       strncpy(tmp->language, p->chan->language, sizeof(tmp->language)-1);
+                       strncpy(tmp->context, p->chan->context, sizeof(tmp->context)-1);
+                       strncpy(tmp->exten, p->chan->exten, sizeof(tmp->exten)-1);
+               } else {
+                       tmp->nativeformats = AST_FORMAT_SLINEAR;
+                       tmp->writeformat = AST_FORMAT_SLINEAR;
+                       tmp->pvt->rawwriteformat = AST_FORMAT_SLINEAR;
+                       tmp->readformat = AST_FORMAT_SLINEAR;
+                       tmp->pvt->rawreadformat = AST_FORMAT_SLINEAR;
+               }
+               if (p->pending)
+                       snprintf(tmp->name, sizeof(tmp->name), "Agent/P%s-%d", p->agent, rand() & 0xffff);
+               else
+                       snprintf(tmp->name, sizeof(tmp->name), "Agent/%s", p->agent);
                tmp->type = type;
                ast_setstate(tmp, state);
-               tmp->writeformat = p->chan->writeformat;
-               tmp->pvt->rawwriteformat = p->chan->writeformat;
-               tmp->readformat = p->chan->readformat;
-               tmp->pvt->rawreadformat = p->chan->readformat;
                tmp->pvt->pvt = p;
                tmp->pvt->send_digit = agent_digit;
                tmp->pvt->call = agent_call;
@@ -277,23 +531,47 @@ static struct ast_channel *agent_new(struct agent_pvt *p, int state)
                tmp->pvt->exception = agent_read;
                tmp->pvt->indicate = agent_indicate;
                tmp->pvt->fixup = agent_fixup;
-               strncpy(tmp->language, p->chan->language, sizeof(tmp->language)-1);
                p->owner = tmp;
                ast_pthread_mutex_lock(&usecnt_lock);
                usecnt++;
                ast_pthread_mutex_unlock(&usecnt_lock);
                ast_update_use_count();
-               strncpy(tmp->context, p->chan->context, sizeof(tmp->context)-1);
-               strncpy(tmp->exten, p->chan->exten, sizeof(tmp->exten)-1);
                tmp->priority = 1;
-               /* Wake up any waiting blockers (by definition the login app) */
-               if (p->chan->blocking) {
-                       pthread_kill(p->chan->blocker, SIGURG);
-                       /* Wait until the blocker releases it */
-                       while(p->chan->blocking)
-                               usleep(1000);
+               /* Wake up and wait for other applications (by definition the login app)
+                * to release this channel). Takes ownership of the agent channel
+                * to this thread only.
+                * For signalling the other thread, ast_queue_frame is used until we
+                * can safely use signals for this purpose. The pselect() needs to be
+                * implemented in the kernel for this.
+                */
+               p->app_sleep_cond = 0;
+               if( pthread_mutex_trylock(&p->app_lock) )
+               {
+                       if (p->chan) {
+                               ast_queue_frame(p->chan, &null_frame, 1);
+                               ast_pthread_mutex_unlock(&p->lock);     /* For other thread to read the condition. */
+                               ast_pthread_mutex_lock(&p->app_lock);
+                               ast_pthread_mutex_lock(&p->lock);
+                       }
+                       if( !p->chan )
+                       {
+                               ast_log(LOG_WARNING, "Agent disconnected while we were connecting the call\n");
+                               p->owner = NULL;
+                               tmp->pvt->pvt = NULL;
+                               p->app_sleep_cond = 1;
+                               ast_channel_free( tmp );
+                               return NULL;
+                       }
+               }
+               p->owning_app = pthread_self();
+               /* After the above step, there should not be any blockers. */
+               if (p->chan) {
+                       if (p->chan->blocking) {
+                               ast_log( LOG_ERROR, "A blocker exists after agent channel ownership acquired\n" );
+                               CRASH;
+                       }
+                       ast_moh_stop(p->chan);
                }
-               ast_moh_stop(p->chan);
        } else
                ast_log(LOG_WARNING, "Unable to allocate channel structure\n");
        return tmp;
@@ -305,6 +583,10 @@ static int read_agent_config(void)
        struct ast_config *cfg;
        struct ast_variable *v;
        struct agent_pvt *p, *pl, *pn;
+       group = 0;
+       autologoff = 0;
+       wrapuptime = 0;
+       ackcall = 1;
        cfg = ast_load(config);
        if (!cfg) {
                ast_log(LOG_NOTICE, "No agent configuration found -- agent support disabled\n");
@@ -321,7 +603,19 @@ static int read_agent_config(void)
        while(v) {
                /* Create the interface list */
                if (!strcasecmp(v->name, "agent")) {
-                       add_agent(v);
+                       add_agent(v->value, 0);
+               } else if (!strcasecmp(v->name, "group")) {
+                       group = ast_get_group(v->value);
+               } else if (!strcasecmp(v->name, "autologoff")) {
+                       autologoff = atoi(v->value);
+                       if (autologoff < 0)
+                               autologoff = 0;
+               } else if (!strcasecmp(v->name, "ackcall")) {
+                        ackcall = ast_true(v->value);
+               } else if (!strcasecmp(v->name, "wrapuptime")) {
+                       wrapuptime = atoi(v->value);
+                       if (wrapuptime < 0)
+                               wrapuptime = 0;
                } else if (!strcasecmp(v->name, "musiconhold")) {
                        strncpy(moh, v->value, sizeof(moh) - 1);
                }
@@ -355,34 +649,166 @@ static int read_agent_config(void)
        return 0;
 }
 
+static int check_availability(struct agent_pvt *newlyavailable, int needlock)
+{
+       struct ast_channel *chan=NULL, *parent=NULL;
+       struct agent_pvt *p;
+       int res;
+       ast_log(LOG_DEBUG, "Checking availability of '%s'\n", newlyavailable->agent);
+       if (needlock)
+               ast_pthread_mutex_lock(&agentlock);
+       p = agents;
+       while(p) {
+               if (p == newlyavailable) {
+                       p = p->next;
+                       continue;
+               }
+               ast_pthread_mutex_lock(&p->lock);
+               if (!p->abouttograb && p->pending && ((p->group && (newlyavailable->group & p->group)) || !strcmp(p->agent, newlyavailable->agent))) {
+                       ast_log(LOG_DEBUG, "Call '%s' looks like a winner for agent '%s'\n", p->owner->name, newlyavailable->agent);
+                       /* We found a pending call, time to merge */
+                       chan = agent_new(newlyavailable, AST_STATE_DOWN);
+                       parent = p->owner;
+                       p->abouttograb = 1;
+                       ast_pthread_mutex_unlock(&p->lock);
+                       break;
+               }
+               ast_pthread_mutex_unlock(&p->lock);
+               p = p->next;
+       }
+       if (needlock)
+               ast_pthread_mutex_unlock(&agentlock);
+       if (parent && chan)  {
+               ast_log( LOG_DEBUG, "Playing beep, lang '%s'\n", newlyavailable->chan->language);
+               res = ast_streamfile(newlyavailable->chan, "beep", newlyavailable->chan->language);
+               ast_log( LOG_DEBUG, "Played beep, result '%d'\n", res);
+               if (!res) {
+                       res = ast_waitstream(newlyavailable->chan, "");
+                       ast_log( LOG_DEBUG, "Waited for stream, result '%d'\n", res);
+               }
+               if (!res) {
+                       /* Note -- parent may have disappeared */
+                       if (p->abouttograb) {
+                               ast_setstate(parent, AST_STATE_UP);
+                               ast_setstate(chan, AST_STATE_UP);
+                               /* Go ahead and mark the channel as a zombie so that masquerade will
+                                  destroy it for us, and we need not call ast_hangup */
+                               ast_pthread_mutex_lock(&parent->lock);
+                               chan->zombie = 1;
+                               ast_channel_masquerade(parent, chan);
+                               ast_pthread_mutex_unlock(&parent->lock);
+                               p->abouttograb = 0;
+                       } else {
+                               ast_log(LOG_DEBUG, "Sneaky, parent disappeared in the mean time...\n");
+                               agent_cleanup(newlyavailable);
+                       }
+               } else {
+                       ast_log(LOG_DEBUG, "Ugh...  Agent hung up at exactly the wrong time\n");
+                       agent_cleanup(newlyavailable);
+               }
+       }
+       return 0;
+}
+
 static struct ast_channel *agent_request(char *type, int format, void *data)
 {
        struct agent_pvt *p;
        struct ast_channel *chan = NULL;
+       char *s;
+       unsigned int groupmatch;
+       int waitforagent=0;
+       s = data;
+       if ((s[0] == '@') && (sscanf(s + 1, "%d", &groupmatch) == 1)) {
+               groupmatch = (1 << groupmatch);
+       } else if ((s[0] == ':') && (sscanf(s + 1, "%d", &groupmatch) == 1)) {
+               groupmatch = (1 << groupmatch);
+               waitforagent = 1;
+       } else {
+               groupmatch = 0;
+       }
+
+       /* Check actual logged in agents first */
        ast_pthread_mutex_lock(&agentlock);
        p = agents;
        while(p) {
-               if (!strcmp(data, p->agent)) {
-                       ast_pthread_mutex_lock(&p->lock);
-                       /* Agent must be registered, but not have any active call */
+               ast_pthread_mutex_lock(&p->lock);
+               if (!p->pending && ((groupmatch && (p->group & groupmatch)) || !strcmp(data, p->agent)) &&
+                               !p->lastdisc.tv_sec && !strlen(p->loginchan)) {
+                       /* Agent must be registered, but not have any active call, and not be in a waiting state */
                        if (!p->owner && p->chan) {
+                               /* Fixed agent */
                                chan = agent_new(p, AST_STATE_DOWN);
+                       } else if (!p->owner && strlen(p->loginchan)) {
+                               /* Adjustable agent */
+                               p->chan = ast_request("Local", format, p->loginchan);
+                               if (p->chan)
+                                       chan = agent_new(p, AST_STATE_DOWN);
+                       }
+                       if (chan) {
+                               ast_pthread_mutex_unlock(&p->lock);
+                               break;
                        }
-                       ast_pthread_mutex_unlock(&p->lock);
-                       break;
                }
+               ast_pthread_mutex_unlock(&p->lock);
                p = p->next;
        }
+       if (!p) {
+               p = agents;
+               while(p) {
+                       ast_pthread_mutex_lock(&p->lock);
+                       if (!p->pending && ((groupmatch && (p->group & groupmatch)) || !strcmp(data, p->agent)) &&
+                                       !p->lastdisc.tv_sec) {
+                               /* Agent must be registered, but not have any active call, and not be in a waiting state */
+                               if (!p->owner && p->chan) {
+                                       /* Fixed agent */
+                                       chan = agent_new(p, AST_STATE_DOWN);
+                               } else if (!p->owner && strlen(p->loginchan)) {
+                                       /* Adjustable agent */
+                                       p->chan = ast_request("Local", format, p->loginchan);
+                                       if (p->chan)
+                                               chan = agent_new(p, AST_STATE_DOWN);
+                               }
+                               if (chan) {
+                                       ast_pthread_mutex_unlock(&p->lock);
+                                       break;
+                               }
+                       }
+                       ast_pthread_mutex_unlock(&p->lock);
+                       p = p->next;
+               }
+       }
+
+       if (!chan && waitforagent) {
+               /* No agent available -- but we're requesting to wait for one.
+                  Allocate a place holder */
+               ast_log(LOG_DEBUG, "Creating place holder for '%s'\n", s);
+               p = add_agent(data, 1);
+               p->group = groupmatch;
+               chan = agent_new(p, AST_STATE_DOWN);
+               if (!chan) {
+                       ast_log(LOG_WARNING, "Weird...  Fix this to drop the unused pending agent\n");
+               }
+       }
        ast_pthread_mutex_unlock(&agentlock);
        return chan;
 }
 
+static int powerof(unsigned int v)
+{
+       int x;
+       for (x=0;x<32;x++) {
+               if (v & (1 << x)) return x;
+       }
+       return 0;
+}
+
 static int agents_show(int fd, int argc, char **argv)
 {
        struct agent_pvt *p;
        char username[256];
        char location[256];
        char talkingto[256];
+       char moh[256];
 
        if (argc != 2)
                return RESULT_SHOWUSAGE;
@@ -390,24 +816,38 @@ static int agents_show(int fd, int argc, char **argv)
        p = agents;
        while(p) {
                ast_pthread_mutex_lock(&p->lock);
-               if (strlen(p->name))
-                       snprintf(username, sizeof(username), "(%s) ", p->name);
-               else
-                       strcpy(username, "");
-               if (p->chan) {
-                       snprintf(location, sizeof(location), "logged in on %s", p->chan->name);
-                       if (p->owner && p->owner->bridge) {
-                               snprintf(talkingto, sizeof(talkingto), " talking to %s", p->owner->bridge->name);
+               if (p->pending) {
+                       if (p->group)
+                               ast_cli(fd, "-- Pending call to group %d\n", powerof(p->group));
+                       else
+                               ast_cli(fd, "-- Pending call to agent %s\n", p->agent);
+               } else {
+                       if (strlen(p->name))
+                               snprintf(username, sizeof(username), "(%s) ", p->name);
+                       else
+                               strcpy(username, "");
+                       if (p->chan) {
+                               snprintf(location, sizeof(location), "logged in on %s", p->chan->name);
+                               if (p->owner && p->owner->bridge) {
+                                       snprintf(talkingto, sizeof(talkingto), " talking to %s", p->owner->bridge->name);
+                               } else {
+                                       strcpy(talkingto, " is idle");
+                               }
+                       } else if (strlen(p->loginchan)) {
+                               snprintf(location, sizeof(location) - 20, "available at '%s'", p->loginchan);
+                               strcpy(talkingto, "");
+                               if (p->acknowledged)
+                                       strcat(location, " (Confirmed)");
                        } else {
-                               strcpy(talkingto, " is idle");
+                               strcpy(location, "not logged in");
+                               strcpy(talkingto, "");
                        }
-               } else {
-                       strcpy(location, "not logged in");
-                       strcpy(talkingto, "");
+                       if (strlen(p->moh))
+                               snprintf(moh, sizeof(moh), " (musiconhold is '%s')", p->moh);
+                       ast_cli(fd, "%-12.12s %s%s%s%s\n", p->agent, 
+                                       username, location, talkingto, moh);
                }
                ast_pthread_mutex_unlock(&p->lock);
-               ast_cli(fd, "%-12.12s %s%s%s\n", p->agent, 
-                               username, location, talkingto);
                p = p->next;
        }
        ast_pthread_mutex_unlock(&agentlock);
@@ -425,33 +865,64 @@ static struct ast_cli_entry cli_show_agents = {
 STANDARD_LOCAL_USER;
 LOCAL_USER_DECL;
 
-static int login_exec(struct ast_channel *chan, void *data)
+static int __login_exec(struct ast_channel *chan, void *data, int callbackmode)
 {
        int res=0;
        int tries = 0;
        struct agent_pvt *p;
        struct localuser *u;
+       struct timeval tv;
        char user[AST_MAX_AGENT];
        char pass[AST_MAX_AGENT];
        char xpass[AST_MAX_AGENT] = "";
        char *errmsg;
+       char info[512];
+       char *opt_user = NULL;
+       char *options = NULL;
+       char *context = NULL;
+       int play_announcement;
+       char *filename = "agent-loginok";
+       
        LOCAL_USER_ADD(u);
+
+       /* Parse the arguments XXX Check for failure XXX */
+       strncpy(info, (char *)data, strlen((char *)data) + AST_MAX_EXTENSION-1);
+       opt_user = info;
+       if( opt_user ) {
+               options = strchr(opt_user, '|');
+               if (options) {
+                       *options = '\0';
+                       options++;
+                       if (callbackmode) {
+                               context = strchr(options, '@');
+                               if (context) {
+                                       *context = '\0';
+                                       context++;
+                               }
+                       }
+               }
+       }
+
        if (chan->_state != AST_STATE_UP)
                res = ast_answer(chan);
-       if (!res)
-               res = ast_app_getdata(chan, "agent-user", user, sizeof(user) - 1, 0);
+       if (!res) {
+               if( opt_user && strlen(opt_user))
+                       strncpy( user, opt_user, AST_MAX_AGENT );
+               else
+                       res = ast_app_getdata(chan, "agent-user", user, sizeof(user) - 1, 0);
+       }
        while (!res && (tries < 3)) {
                /* Check for password */
                ast_pthread_mutex_lock(&agentlock);
                p = agents;
                while(p) {
-                       if (!strcmp(p->agent, user))
+                       if (!strcmp(p->agent, user) && !p->pending)
                                strncpy(xpass, p->password, sizeof(xpass) - 1);
                        p = p->next;
                }
                ast_pthread_mutex_unlock(&agentlock);
                if (!res) {
-                       if (strlen(xpass) || !p)
+                       if (strlen(xpass))
                                res = ast_app_getdata(chan, "agent-pass", pass, sizeof(pass) - 1, 0);
                        else
                                strcpy(pass, "");
@@ -468,9 +939,31 @@ static int login_exec(struct ast_channel *chan, void *data)
                while(p) {
                        ast_pthread_mutex_lock(&p->lock);
                        if (!strcmp(p->agent, user) &&
-                               !strcmp(p->password, pass)) {
+                               !strcmp(p->password, pass) && !p->pending) {
                                        if (!p->chan) {
-                                               res = ast_streamfile(chan, "agent-loginok", chan->language);
+                                               if (callbackmode) {
+                                                       char tmpchan[256] = "";
+                                                       /* Retrieve login chan */
+                                                       res = ast_app_getdata(chan, "agent-newlocation", tmpchan, sizeof(tmpchan) - 1, 0);
+                                                       if (!res) {
+                                                               if (context && strlen(context) && strlen(tmpchan))
+                                                                       snprintf(p->loginchan, sizeof(p->loginchan), "%s@%s", tmpchan, context);
+                                                               else
+                                                                       strncpy(p->loginchan, tmpchan, sizeof(p->loginchan) - 1);
+                                                               if (!strlen(p->loginchan))
+                                                                       filename = "agent-loggedoff";
+                                                               p->acknowledged = 0;
+                                                       }
+                                               } else {
+                                                       strcpy(p->loginchan, "");
+                                                       p->acknowledged = 0;
+                                               }
+                                               play_announcement = 1;
+                                               if( options )
+                                                       if( strchr( options, 's' ) )
+                                                               play_announcement = 0;
+                                               if( !res && play_announcement )
+                                                       res = ast_streamfile(chan, filename, chan->language);
                                                if (!res)
                                                        ast_waitstream(chan, "");
                                                if (!res) {
@@ -486,7 +979,21 @@ static int login_exec(struct ast_channel *chan, void *data)
                                                /* Check once more just in case */
                                                if (p->chan)
                                                        res = -1;
-                                               if (!res) {
+                                               if (callbackmode && !res) {
+                                                       /* Just say goodbye and be done with it */
+                                                       if (!res)
+                                                               res = ast_safe_sleep(chan, 500);
+                                                       res = ast_streamfile(chan, "vm-goodbye", chan->language);
+                                                       if (!res)
+                                                               res = ast_waitstream(chan, "");
+                                                       if (!res)
+                                                               res = ast_safe_sleep(chan, 1000);
+                                                       ast_pthread_mutex_unlock(&p->lock);
+                                                       ast_pthread_mutex_unlock(&agentlock);
+                                               } else if (!res) {
+                                                       /* check if the moh class was changed with setmusiconhold */
+                                                       if (*(chan->musicclass))
+                                                               strncpy(p->moh, chan->musicclass, sizeof(p->moh) - 1);
                                                        ast_moh_start(chan, p->moh);
                                                        manager_event(EVENT_FLAG_AGENT, "Agentlogin",
                                                                "Agent: %s\r\n"
@@ -498,21 +1005,49 @@ static int login_exec(struct ast_channel *chan, void *data)
                                                        /* Login this channel and wait for it to
                                                           go away */
                                                        p->chan = chan;
+                                                       p->acknowledged = 1;
+                                                       check_availability(p, 0);
                                                        ast_pthread_mutex_unlock(&p->lock);
                                                        ast_pthread_mutex_unlock(&agentlock);
-                                                       while ((res >= 0) && (p->chan == chan)) {
-                                                               /* True sleep here, since we're being monitored
-                                                                  elsewhere instead */
-                                                               if (p->owner) 
-                                                                       sleep(1);
-                                                               else
-                                                                       res = ast_safe_sleep(chan, 1000);
+                                                       while (res >= 0) {
+                                                               ast_pthread_mutex_lock(&p->lock);
+                                                               if (p->chan != chan)
+                                                                       res = -1;
+                                                               ast_pthread_mutex_unlock(&p->lock);
+                                                               /* Yield here so other interested threads can kick in. */
+                                                               sched_yield();
+                                                               if (res)
+                                                                       break;
+
+                                                               ast_pthread_mutex_lock(&p->lock);
+                                                               if (p->lastdisc.tv_sec) {
+                                                                       gettimeofday(&tv, NULL);
+                                                                       if ((tv.tv_sec - p->lastdisc.tv_sec) * 1000 + 
+                                                                               (tv.tv_usec - p->lastdisc.tv_usec) / 1000 > p->wrapuptime) {
+                                                                                       ast_log(LOG_DEBUG, "Wrapup time expired!\n");
+                                                                               memset(&p->lastdisc, 0, sizeof(p->lastdisc));
+                                                                               check_availability(p, 1);
+                                                                       }
+                                                               }
+                                                               ast_pthread_mutex_unlock(&p->lock);
+                                                               /*      Synchronize channel ownership between call to agent and itself. */
+                                                               pthread_mutex_lock( &p->app_lock );
+                                                               ast_pthread_mutex_lock(&p->lock);
+                                                               p->owning_app = pthread_self();
+                                                               ast_pthread_mutex_unlock(&p->lock);
+                                                               res = ast_safe_sleep_conditional( chan, 1000,
+                                                                                                               agent_cont_sleep, p );
+                                                               pthread_mutex_unlock( &p->app_lock );
+                                                               sched_yield();
                                                        }
+                                                       ast_pthread_mutex_lock(&p->lock);
                                                        if (res && p->owner) 
                                                                ast_log(LOG_WARNING, "Huh?  We broke out when there was still an owner?\n");
                                                        /* Log us off if appropriate */
                                                        if (p->chan == chan)
                                                                p->chan = NULL;
+                                                       p->acknowledged = 0;
+                                                       ast_pthread_mutex_unlock(&p->lock);
                                                        if (option_verbose > 2)
                                                                ast_verbose(VERBOSE_PREFIX_3 "Agent '%s' logged out\n", p->agent);
                                                        manager_event(EVENT_FLAG_AGENT, "Agentlogoff",
@@ -522,6 +1057,10 @@ static int login_exec(struct ast_channel *chan, void *data)
                                                        if (p->dead && !p->owner)
                                                                free(p);
                                                }
+                                               else {
+                                                       ast_pthread_mutex_unlock(&p->lock);
+                                                       p = NULL;
+                                               }
                                                res = -1;
                                        } else {
                                                ast_pthread_mutex_unlock(&p->lock);
@@ -545,6 +1084,15 @@ static int login_exec(struct ast_channel *chan, void *data)
        return -1;
 }
 
+static int login_exec(struct ast_channel *chan, void *data)
+{
+       return __login_exec(chan, data, 0);
+}
+
+static int callback_exec(struct ast_channel *chan, void *data)
+{
+       return __login_exec(chan, data, 1);
+}
 
 int load_module()
 {
@@ -554,6 +1102,7 @@ int load_module()
                return -1;
        }
        ast_register_application(app, login_exec, synopsis, descrip);
+       ast_register_application(app2, callback_exec, synopsis2, descrip2);
        ast_cli_register(&cli_show_agents);
        /* Read in the config */
        read_agent_config();
@@ -572,6 +1121,7 @@ int unload_module()
        /* First, take us out of the channel loop */
        ast_cli_unregister(&cli_show_agents);
        ast_unregister_application(app);
+       ast_unregister_application(app2);
        ast_channel_unregister(type);
        if (!ast_pthread_mutex_lock(&agentlock)) {
                /* Hangup all interfaces if they have an owner */