Skip to content
Snippets Groups Projects
chan_agent.c 65.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • Mark Spencer's avatar
    Mark Spencer committed
    /*
     * Asterisk -- A telephony toolkit for Linux.
     *
    
     * Implementation of Agents
    
    Mark Spencer's avatar
    Mark Spencer committed
     * 
    
     * Copyright (C) 1999 - 2005, Digium Inc.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * Mark Spencer <markster@digium.com>
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
     * This program is free software, distributed under the terms of
     * the GNU General Public License
     */
    
    #include <stdio.h>
    #include <string.h>
    #include <asterisk/lock.h>
    #include <asterisk/channel.h>
    #include <asterisk/config.h>
    #include <asterisk/logger.h>
    #include <asterisk/module.h>
    #include <asterisk/pbx.h>
    #include <asterisk/options.h>
    #include <asterisk/lock.h>
    #include <asterisk/sched.h>
    #include <asterisk/io.h>
    #include <asterisk/rtp.h>
    #include <asterisk/acl.h>
    #include <asterisk/callerid.h>
    #include <asterisk/file.h>
    #include <asterisk/cli.h>
    #include <asterisk/app.h>
    #include <asterisk/musiconhold.h>
    #include <asterisk/manager.h>
    
    #include <asterisk/utils.h>
    
    #include <asterisk/astdb.h>
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include <sys/socket.h>
    #include <errno.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <netdb.h>
    #include <arpa/inet.h>
    #include <sys/signal.h>
    
    
    static const char desc[] = "Agent Proxy Channel";
    static const char channeltype[] = "Agent";
    static const char tdesc[] = "Call Agent Proxy Channel";
    static const char config[] = "agents.conf";
    
    static const char app[] = "AgentLogin";
    static const char app2[] = "AgentCallbackLogin";
    static const char app3[] = "AgentMonitorOutgoing";
    
    static const char synopsis[] = "Call agent login";
    static const char synopsis2[] = "Call agent callback login";
    static const char synopsis3[] = "Record agent's outgoing call";
    
    Mark Spencer's avatar
    Mark Spencer committed
    "  AgentLogin([AgentNo][|options]):\n"
    
    Mark Spencer's avatar
    Mark Spencer committed
    "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"
    
    Mark Spencer's avatar
    Mark Spencer committed
    "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 after agent logged in/off\n";
    
    Mark Spencer's avatar
    Mark Spencer committed
    "  AgentCallbackLogin([AgentNo][|[options][exten]@context]):\n"
    
    "Asks the agent to login to the system with callback.\n"
    
    "The agent's callback extension is called (optionally with the specified\n"
    
    "The option string may contain zero or more of the following characters:\n"
    "      's' -- silent login - do not announce the login ok segment agent logged in/off\n";
    
    
    "Tries to figure out the id of the agent who is placing outgoing call based on\n"
    "comparision of the callerid of the current interface and the global variable \n"
    "placed by the AgentCallbackLogin application. That's why it should be used only\n"
    "with the AgentCallbackLogin app. Uses the monitoring functions in chan_agent \n"
    "instead of Monitor application. That have to be configured in the agents.conf file.\n"
    "\nReturn value:\n"
    "Normally the app returns 0 unless the options are passed. Also if the callerid or\n"
    "the agentid are not specified it'll look for n+101 priority.\n"
    "\nOptions:\n"
    "	'd' - make the app return -1 if there is an error condition and there is\n"
    "	      no extension n+101\n"
    
    "	'c' - change the CDR so that the source of the call is 'Agent/agent_id'\n"
    
    "	'n' - don't generate the warnings when there is no callerid or the\n"
    "	      agentid is not known.\n"
    "             It's handy if you want to have one context for agent and non-agent calls.\n";
    
    static const char mandescr_agents[] =
    
    "Description: Will list info about all possible agents.\n"
    "Variables: NONE\n";
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static char moh[80] = "default";
    
    #define AST_MAX_AGENT	80		/* Agent ID or Password max length */
    
    #define AST_MAX_FILENAME_LEN	256
    
    /* Persistent Agents astdb family */
    
    static const char pa_family[] = "/Agents";
    
    /* The maximum lengh of each persistent member agent database entry */
    #define PA_MAX_LEN 2048
    /* queues.conf [general] option */
    static int persistent_agents = 0;
    static void dump_agents(void);
    
    
    static ast_group_t group;
    
    static int autologoff;
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int wrapuptime;
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int ackcall;
    
    static int maxlogintries = 3;
    static char agentgoodbye[AST_MAX_FILENAME_LEN] = "vm-goodbye";
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int usecnt =0;
    
    AST_MUTEX_DEFINE_STATIC(usecnt_lock);
    
    /* Protect the interface list (of pvt's) */
    
    AST_MUTEX_DEFINE_STATIC(agentlock);
    
    static int recordagentcalls = 0;
    
    static char recordformat[AST_MAX_BUF] = "";
    static char recordformatext[AST_MAX_BUF] = "";
    
    static int createlink = 0;
    
    static char urlprefix[AST_MAX_BUF] = "";
    static char savecallsin[AST_MAX_BUF] = "";
    
    static int updatecdr = 0;
    
    static char beep[AST_MAX_BUF] = "beep";
    
    #define GETAGENTBYCALLERID	"AGENTBYCALLERID"
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static struct agent_pvt {
    
    	ast_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 loginstart;			/* When agent first logged in (0 when logged off) */
    	time_t start;				/* When call started */
    	struct timeval lastdisc;		/* When last disconnected */
    	int wrapuptime;				/* Wrapup time in ms */
    
    	ast_group_t group;		/* Group memberships */
    
    	int acknowledged;			/* Acknowledged */
    	char moh[80];				/* Which music on hold */
    	char agent[AST_MAX_AGENT];		/* Agent ID */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char password[AST_MAX_AGENT];		/* Password for Agent login */
    	char name[AST_MAX_AGENT];
    
    	ast_mutex_t app_lock;			/* Synchronization between owning applications */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	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 */
    
    Mark Spencer's avatar
    Mark Spencer committed
    } *agents = NULL;
    
    
    #define CHECK_FORMATS(ast, p) do { \
    	if (p->chan) {\
    		if (ast->nativeformats != p->chan->nativeformats) { \
    			ast_log(LOG_DEBUG, "Native formats changing from %d to %d\n", ast->nativeformats, p->chan->nativeformats); \
    			/* Native formats changed, reset things */ \
    			ast->nativeformats = p->chan->nativeformats; \
    			ast_log(LOG_DEBUG, "Resetting read to %d and write to %d\n", ast->readformat, ast->writeformat);\
    
    			ast_set_read_format(ast, ast->readformat); \
    			ast_set_write_format(ast, ast->writeformat); \
    
    		if (p->chan->readformat != ast->rawreadformat)  \
    			ast_set_read_format(p->chan, ast->rawreadformat); \
    		if (p->chan->writeformat != ast->rawwriteformat) \
    			ast_set_write_format(p->chan, ast->rawwriteformat); \
    
    	} \
    } while(0)
    
    
    /* Cleanup moves all the relevant FD's from the 2nd to the first, but retains things
       properly for a timingfd XXX This might need more work if agents were logged in as agents or other
       totally impractical combinations XXX */
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    #define CLEANUP(ast, p) do { \
    	int x; \
    	if (p->chan) { \
    
    		for (x=0;x<AST_MAX_FDS;x++) {\
    			if (x != AST_MAX_FDS - 2) \
    				ast->fds[x] = p->chan->fds[x]; \
    		} \
    		ast->fds[AST_MAX_FDS - 3] = p->chan->fds[AST_MAX_FDS - 2]; \
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} \
    } while(0)
    
    
    static struct ast_channel *agent_request(const char *type, int format, void *data, int *cause);
    static int agent_devicestate(void *data);
    static int agent_digit(struct ast_channel *ast, char digit);
    static int agent_call(struct ast_channel *ast, char *dest, int timeout);
    static int agent_hangup(struct ast_channel *ast);
    static int agent_answer(struct ast_channel *ast);
    static struct ast_frame *agent_read(struct ast_channel *ast);
    static int agent_write(struct ast_channel *ast, struct ast_frame *f);
    static int agent_sendhtml(struct ast_channel *ast, int subclass, char *data, int datalen);
    static int agent_indicate(struct ast_channel *ast, int condition);
    static int agent_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
    static struct ast_channel *agent_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge);
    
    static const struct ast_channel_tech agent_tech = {
    	.type = channeltype,
    	.description = tdesc,
    	.capabilities = -1,
    	.requester = agent_request,
    	.devicestate = agent_devicestate,
    	.send_digit = agent_digit,
    	.call = agent_call,
    	.hangup = agent_hangup,
    	.answer = agent_answer,
    	.read = agent_read,
    	.write = agent_write,
    	.send_html = agent_sendhtml,
    	.exception = agent_read,
    	.indicate = agent_indicate,
    	.fixup = agent_fixup,
    	.bridged_channel = agent_bridgedchannel,
    };
    
    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)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	char tmp[AST_MAX_BUF] = "";
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char *password=NULL, *name=NULL;
    
    	strncpy(tmp, agent, sizeof(tmp) - 1);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if ((password = strchr(tmp, ','))) {
    		*password = '\0';
    		password++;
    		while (*password < 33) password++;
    	}
    	if (password && (name = strchr(password, ','))) {
    		*name = '\0';
    		name++;
    		while (*name < 33) name++; 
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	p = agents;
    	while(p) {
    
    		if (!pending && !strcmp(p->agent, tmp))
    
    Mark Spencer's avatar
    Mark Spencer committed
    			break;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		p = p->next;
    	}
    	if (!p) {
    		p = malloc(sizeof(struct agent_pvt));
    		if (p) {
    			memset(p, 0, sizeof(struct agent_pvt));
    			strncpy(p->agent, tmp, sizeof(p->agent) -1);
    
    			ast_mutex_init(&p->lock);
    			ast_mutex_init(&p->app_lock);
    
    			p->owning_app = (pthread_t) -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    			p->app_sleep_cond = 1;
    
    			p->pending = pending;
    			p->next = NULL;
    			if (prev)
    				prev->next = p;
    			else
    				agents = p;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	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);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	p->ackcall = ackcall;
    
    	p->autologoff = autologoff;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	p->wrapuptime = wrapuptime;
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int agent_cleanup(struct agent_pvt *p)
    {
    	struct ast_channel *chan = p->owner;
    	p->owner = NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	p->app_sleep_cond = 1;
    	/* Release ownership of the agent to other threads (presumably running the login app). */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (chan)
    		ast_channel_free(chan);
    
    	if (p->dead) {
    		ast_mutex_destroy(&p->lock);
    		ast_mutex_destroy(&p->app_lock);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		free(p);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return 0;
    }
    
    
    static int check_availability(struct agent_pvt *newlyavailable, int needlock);
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int agent_answer(struct ast_channel *ast)
    {
    	ast_log(LOG_WARNING, "Huh?  Agent is being asked to answer?\n");
    	return -1;
    }
    
    
    static int __agent_start_monitoring(struct ast_channel *ast, struct agent_pvt *p, int needlock)
    
    {
    	char tmp[AST_MAX_BUF],tmp2[AST_MAX_BUF], *pointer;
    	char filename[AST_MAX_BUF];
    	int res = -1;
    	if (!p)
    		return -1;
    	if (!ast->monitor) {
    		snprintf(filename, sizeof(filename), "agent-%s-%s",p->agent, ast->uniqueid);
    
    		/* substitute . for - */
    		if ((pointer = strchr(filename, '.')))
    
    		snprintf(tmp, sizeof(tmp), "%s%s",savecallsin ? savecallsin : "", filename);
    
    		ast_monitor_start(ast, recordformat, tmp, needlock);
    		ast_monitor_setjoinfiles(ast, 1);
    		snprintf(tmp2, sizeof(tmp2), "%s%s.%s", urlprefix ? urlprefix : "", filename, recordformatext);
    #if 0
    		ast_verbose("name is %s, link is %s\n",tmp, tmp2);
    #endif
    		if (!ast->cdr)
    			ast->cdr = ast_cdr_alloc();
    		ast_cdr_setuserfield(ast, tmp2);
    		res = 0;
    	} else
    		ast_log(LOG_ERROR, "Recording already started on that call.\n");
    	return res;
    }
    
    
    static int agent_start_monitoring(struct ast_channel *ast, int needlock)
    {
    
    	return __agent_start_monitoring(ast, ast->tech_pvt, needlock);
    
    static struct ast_frame *agent_read(struct ast_channel *ast)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct agent_pvt *p = ast->tech_pvt;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	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 };
    
    	CHECK_FORMATS(ast, p);
    
    	if (p->chan) {
    
    		ast_copy_flags(p->chan, ast, AST_FLAG_EXCEPTION);
    
    		if (ast->fdno == AST_MAX_FDS - 3)
    			p->chan->fdno = AST_MAX_FDS - 2;
    		else
    			p->chan->fdno = ast->fdno;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		f = ast_read(p->chan);
    
    	} else
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (!f) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* If there's a channel, hang it up  (if it's on a callback) make it NULL */
    
    			p->chan->_bridge = NULL;
    
    			/* Note that we don't hangup if it's not a callback because Asterisk will do it
    			   for us when the PBX instance that called login finishes */
    
    			if (!ast_strlen_zero(p->loginchan)) {
    
    				if (p->chan)
    					ast_log(LOG_DEBUG, "Bridge on '%s' being cleared (2)\n", p->chan->name);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				ast_hangup(p->chan);
    
    				if (p->wrapuptime && p->acknowledged) {
    
    					gettimeofday(&p->lastdisc, NULL);
    					p->lastdisc.tv_usec += (p->wrapuptime % 1000) * 1000;
    					if (p->lastdisc.tv_usec > 1000000) {
    						p->lastdisc.tv_usec -= 1000000;
    						p->lastdisc.tv_sec++;
    					}
    					p->lastdisc.tv_sec += (p->wrapuptime / 1000);
    				}
    			}
    
    Mark Spencer's avatar
    Mark Spencer committed
    			p->chan = NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    			p->acknowledged = 0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	if (p->chan && f && (f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_ANSWER)) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    /* TC */
    
    		ast_log(LOG_DEBUG, "Got answer on %s\n", p->chan->name);
    
    		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;
    
    			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;
    		}
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (f && (f->frametype == AST_FRAME_DTMF) && (f->subclass == '*')) {
    		/* * terminates call */
    		ast_frfree(f);
    		f = NULL;
    	}
    
    	if (f && (f->frametype == AST_FRAME_VOICE) && !p->acknowledged) {
    		/* Don't pass along agent audio until call is acknowledged */
    		ast_frfree(f);
    		f = &null_frame;
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	CLEANUP(ast,p);
    
    	if (p->chan && !p->chan->_bridge) {
    		if (strcasecmp(p->chan->type, "Local")) {
    			p->chan->_bridge = ast;
    			if (p->chan)
    				ast_log(LOG_DEBUG, "Bridge on '%s' being set to '%s' (3)\n", p->chan->name, p->chan->_bridge->name);
    		}
    	}
    
    	if (recordagentcalls && f == &answer_frame)
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return f;
    }
    
    
    static int agent_sendhtml(struct ast_channel *ast, int subclass, char *data, int datalen)
    {
    
    	struct agent_pvt *p = ast->tech_pvt;
    
    	int res = -1;
    	ast_mutex_lock(&p->lock);
    	if (p->chan) 
    		res = ast_channel_sendhtml(p->chan, subclass, data, datalen);
    	ast_mutex_unlock(&p->lock);
    	return res;
    }
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int agent_write(struct ast_channel *ast, struct ast_frame *f)
    {
    
    	struct agent_pvt *p = ast->tech_pvt;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int res = -1;
    
    	CHECK_FORMATS(ast, p);
    
    	if (p->chan) {
    
    		if ((f->frametype != AST_FRAME_VOICE) ||
    			(f->subclass == p->chan->writeformat)) {
    			res = ast_write(p->chan, f);
    		} else {
    			ast_log(LOG_DEBUG, "Dropping one incompatible voice frame on '%s' to '%s'\n", ast->name, p->chan->name);
    			res = 0;
    		}
    
    	} else
    
    Mark Spencer's avatar
    Mark Spencer committed
    	CLEANUP(ast, p);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    
    static int agent_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct agent_pvt *p = newchan->tech_pvt;
    
    	ast_mutex_lock(&p->lock);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (p->owner != oldchan) {
    		ast_log(LOG_WARNING, "old channel wasn't %p but was %p\n", oldchan, p->owner);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return -1;
    	}
    	p->owner = newchan;
    
    	ast_mutex_unlock(&p->lock);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return 0;
    }
    
    static int agent_indicate(struct ast_channel *ast, int condition)
    {
    
    	struct agent_pvt *p = ast->tech_pvt;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int res = -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (p->chan)
    		res = ast_indicate(p->chan, condition);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    static int agent_digit(struct ast_channel *ast, char digit)
    {
    
    	struct agent_pvt *p = ast->tech_pvt;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int res = -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (p->chan)
    
    		res = p->chan->tech->send_digit(p->chan, digit);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    static int agent_call(struct ast_channel *ast, char *dest, int timeout)
    {
    
    	struct agent_pvt *p = ast->tech_pvt;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int res = -1;
    
    	int newstate=0;
    
    	if (!p->chan) {
    		if (p->pending) {
    			ast_log(LOG_DEBUG, "Pretending to dial on pending agent\n");
    
    			newstate = 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;
    		}
    
    		if (newstate)
    			ast_setstate(ast, newstate);
    
    	} else if (!ast_strlen_zero(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);
    
    		if (p->chan->cid.cid_num)
    			free(p->chan->cid.cid_num);
    		if (ast->cid.cid_num)
    			p->chan->cid.cid_num = strdup(ast->cid.cid_num);
    
    			p->chan->cid.cid_num = NULL;
    		if (p->chan->cid.cid_name)
    			free(p->chan->cid.cid_name);
    		if (ast->cid.cid_name)
    			p->chan->cid.cid_name = strdup(ast->cid.cid_name);
    		else
    			p->chan->cid.cid_name = NULL;
    
    		ast_channel_inherit_variables(ast, p->chan);
    
    		res = ast_call(p->chan, p->loginchan, 0);
    		CLEANUP(ast,p);
    
    	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);
    
    	ast_log( LOG_DEBUG, "Played beep, result '%d'\n", res);
    	if (!res) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		res = ast_waitstream(p->chan, "");
    
    		ast_log( LOG_DEBUG, "Waited for stream, result '%d'\n", res);
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	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);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (res)
    
    			ast_log(LOG_WARNING, "Unable to set read format to %s\n", ast_getformatname(ast_best_codec(p->chan->nativeformats)));
    
    		/* Agent hung-up */
    
    		p->chan = NULL;
    	}
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    	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);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (res)
    
    			ast_log(LOG_WARNING, "Unable to set write format to %s\n", ast_getformatname(ast_best_codec(p->chan->nativeformats)));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	if( !res )
    	{
    
    		/* Call is immediately up, or might need ack */
    		if (p->ackcall > 1)
    
    			newstate = AST_STATE_RINGING;
    
    			newstate = AST_STATE_UP;
    
    			if (recordagentcalls)
    				agent_start_monitoring(ast,0);
    
    			p->acknowledged = 1;
    		}
    		res = 0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	CLEANUP(ast,p);
    
    	if (newstate)
    		ast_setstate(ast, newstate);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    static int agent_hangup(struct ast_channel *ast)
    {
    
    	struct agent_pvt *p = ast->tech_pvt;
    
    	int howlong = 0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	p->owner = NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	p->app_sleep_cond = 1;
    
    
    	/* if they really are hung up then set start to 0 so the test
    	 * later if we're called on an already downed channel
    	 * doesn't cause an agent to be logged out like when
    	 * agent_request() is followed immediately by agent_hangup()
    	 * as in apps/app_chanisavail.c:chanavail_exec()
    	 */
    
    
    	ast_mutex_lock(&usecnt_lock);
    	usecnt--;
    	ast_mutex_unlock(&usecnt_lock);
    
    
    	ast_log(LOG_DEBUG, "Hangup called for state %s\n", ast_state2str(ast->_state));
    	if (p->start && (ast->_state != AST_STATE_UP)) {
    
    		howlong = time(NULL) - p->start;
    
    		p->start = 0;
    	} else if (ast->_state == AST_STATE_RESERVED) {
    		howlong = 0;
    	} else
    		p->start = 0; 
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (p->chan) {
    
    		p->chan->_bridge = NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* If they're dead, go ahead and hang up on the agent now */
    
    		if (!ast_strlen_zero(p->loginchan)) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			/* Store last disconnect time */
    
    			if (p->wrapuptime && p->acknowledged) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    				gettimeofday(&p->lastdisc, NULL);
    				p->lastdisc.tv_usec += (p->wrapuptime % 1000) * 1000;
    				if (p->lastdisc.tv_usec >= 1000000) {
    					p->lastdisc.tv_usec -= 1000000;
    					p->lastdisc.tv_sec++;
    				}
    				p->lastdisc.tv_sec += (p->wrapuptime / 1000);
    			} else
    				memset(&p->lastdisc, 0, sizeof(p->lastdisc));
    
    			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)) {
    
    				char agent[AST_MAX_AGENT] = "";
    				long logintime = time(NULL) - p->loginstart;
    				p->loginstart = 0;
    
    				ast_log(LOG_NOTICE, "Agent '%s' didn't answer/confirm within %d seconds (waited %d)\n", p->name, p->autologoff, howlong);
    
    				manager_event(EVENT_FLAG_AGENT, "Agentcallbacklogoff",
    					"Agent: %s\r\n"
    					"Loginchan: %s\r\n"
    					"Logintime: %ld\r\n"
    					"Reason: Autologoff\r\n"
    					"Uniqueid: %s\r\n",
    					p->agent, p->loginchan, logintime, ast->uniqueid);
    				snprintf(agent, sizeof(agent), "Agent/%s", p->agent);
    				ast_queue_log("NONE", ast->uniqueid, agent, "AGENTCALLBACKLOGOFF", "%s|%ld|%s", p->loginchan, logintime, "Autologoff");
    
    				p->loginchan[0] = '\0';
    
    Mark Spencer's avatar
    Mark Spencer committed
    			    ast_device_state_changed("Agent/%s", p->agent);
    
    		} else if (p->dead) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			ast_softhangup(p->chan, AST_SOFTHANGUP_EXPLICIT);
    
    			ast_device_state_changed("Agent/%s", p->agent);
    
    			ast_moh_start(p->chan, p->moh);
    
    		/* Release ownership of the agent to other threads (presumably running the login app). */
    
    	} else if (p->dead) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* Go ahead and lose it */
    
    		/* Release ownership of the agent to other threads (presumably running the login app). */
    
    	} else {
    
    		/* Release ownership of the agent to other threads (presumably running the login app). */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	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) {
    
    		ast_mutex_destroy(&p->lock);
    		ast_mutex_destroy(&p->app_lock);
    
    	} else {
    		if (p->chan) {
    			/* Not dead -- check availability now */
    			ast_mutex_lock(&p->lock);
    			/* Store last disconnect time */
    			gettimeofday(&p->lastdisc, NULL);
    			ast_mutex_unlock(&p->lock);
    		}
    
    		/* Release ownership of the agent to other threads (presumably running the login app). */
    		ast_mutex_unlock(&p->app_lock);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return 0;
    }
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int agent_cont_sleep( void *data )
    {
    	struct agent_pvt *p;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct timeval tv;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int res;
    
    	p = (struct agent_pvt *)data;
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    	res = p->app_sleep_cond;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	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;
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if( !res )
    		ast_log( LOG_DEBUG, "agent_cont_sleep() returning %d\n", res );
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    
    static int agent_ack_sleep( void *data )
    {
    	struct agent_pvt *p;
    
    	struct ast_frame *f;
    
    
    	/* Wait a second and look for something */
    
    	p = (struct agent_pvt *)data;
    	if (p->chan) {
    		for(;;) {
    			to = ast_waitfor(p->chan, to);
    			if (to < 0) {
    				res = -1;
    				break;
    			}
    			if (!to) {
    				res = 0;
    				break;
    			}
    
    			f = ast_read(p->chan);
    			if (!f) {
    				res = -1;
    				break;
    			}
    			if (f->frametype == AST_FRAME_DTMF)
    				res = f->subclass;
    			else
    				res = 0;
    			ast_frfree(f);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			if (!p->app_sleep_cond) {
    
    				ast_mutex_unlock(&p->lock);
    				res = 0;
    				break;
    			} else if (res == '#') {
    				ast_mutex_unlock(&p->lock);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				res = 1;
    
    				break;
    			}
    			ast_mutex_unlock(&p->lock);
    			res = 0;
    		}
    	} else
    		res = -1;
    	return res;
    }
    
    
    static struct ast_channel *agent_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge)
    {
    	struct agent_pvt *p;
    	struct ast_channel *ret=NULL;
    
    	if (chan == p->chan)
    		ret = bridge->_bridge;
    	else if (chan == bridge->_bridge)
    		ret = p->chan;
    
    	if (option_debug)
    		ast_log(LOG_DEBUG, "Asked for bridged channel on '%s'/'%s', returning '%s'\n", chan->name, bridge->name, ret ? ret->name : "<none>");
    	return ret;
    
    /*--- agent_new: Create new agent channel ---*/
    
    Mark Spencer's avatar
    Mark Spencer committed
    static struct ast_channel *agent_new(struct agent_pvt *p, int state)
    {
    	struct ast_channel *tmp;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct ast_frame null_frame = { AST_FRAME_NULL };
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (!p->chan) {
    		ast_log(LOG_WARNING, "No channel? :(\n");
    		return NULL;
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	tmp = ast_channel_alloc(0);
    	if (tmp) {
    
    		if (p->chan) {
    			tmp->nativeformats = p->chan->nativeformats;
    			tmp->writeformat = p->chan->writeformat;
    
    			tmp->rawwriteformat = p->chan->writeformat;
    
    			tmp->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->rawwriteformat = AST_FORMAT_SLINEAR;
    
    			tmp->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);
    
    		/* Safe, agentlock already held */
    
    Mark Spencer's avatar
    Mark Spencer committed
    		ast_setstate(tmp, state);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		p->owner = tmp;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		usecnt++;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		ast_update_use_count();
    		tmp->priority = 1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* 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( ast_mutex_trylock(&p->app_lock) )
    
    Mark Spencer's avatar
    Mark Spencer committed
    		{
    
    			if (p->chan) {
    
    				ast_mutex_unlock(&p->lock);	/* For other thread to read the condition. */
    				ast_mutex_lock(&p->app_lock);
    				ast_mutex_lock(&p->lock);
    
    			if( !p->chan )
    			{
    				ast_log(LOG_WARNING, "Agent disconnected while we were connecting the call\n");
    				p->owner = NULL;
    
    				p->app_sleep_cond = 1;
    				ast_channel_free( tmp );
    
    				ast_mutex_unlock(&p->lock);	/* For other thread to read the condition. */
    				ast_mutex_unlock(&p->app_lock);
    
    				return NULL;
    			}
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    		p->owning_app = pthread_self();
    		/* After the above step, there should not be any blockers. */
    
    			if (ast_test_flag(p->chan, AST_FLAG_BLOCKING)) {
    
    				ast_log( LOG_ERROR, "A blocker exists after agent channel ownership acquired\n" );
    				CRASH;
    			}
    			ast_moh_stop(p->chan);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    	} else
    
    		ast_log(LOG_WARNING, "Unable to allocate agent channel structure\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return tmp;
    }
    
    
    
    /*--- read_agent_config: Read configuration data (agents.conf) ---*/
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int read_agent_config(void)
    {
    	struct ast_config *cfg;
    	struct ast_variable *v;
    	struct agent_pvt *p, *pl, *pn;
    
    	char *general_val;
    
    	autologoff = 0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	wrapuptime = 0;
    
    	cfg = ast_config_load(config);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (!cfg) {
    		ast_log(LOG_NOTICE, "No agent configuration found -- agent support disabled\n");
    		return 0;
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	p = agents;
    	while(p) {
    		p->dead = 1;
    		p = p->next;
    	}
    
    	strncpy(moh, "default", sizeof(moh) - 1);
    
    	/* set the default recording values */
    	recordagentcalls = 0;
    	createlink = 0;
    
    	strncpy(recordformat, "wav", sizeof(recordformat) - 1);
    	strncpy(recordformatext, "wav", sizeof(recordformatext) - 1);
    	urlprefix[0] = '\0';
    	savecallsin[0] = '\0';
    
    	/* Read in [general] section for persistance */
    	if ((general_val = ast_variable_retrieve(cfg, "general", "persistentagents")))
    		persistent_agents = ast_true(general_val);
    
    	/* Read in the [agents] section */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	v = ast_variable_browse(cfg, "agents");
    	while(v) {
    		/* Create the interface list */
    		if (!strcasecmp(v->name, "agent")) {
    
    		} 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;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		} else if (!strcasecmp(v->name, "ackcall")) {
    
    			if (!strcasecmp(v->value, "always"))
    				ackcall = 2;
    			else if (ast_true(v->value))
    
    Mark Spencer's avatar
    Mark Spencer committed
    		} else if (!strcasecmp(v->name, "wrapuptime")) {
    			wrapuptime = atoi(v->value);
    			if (wrapuptime < 0)
    				wrapuptime = 0;
    
    		} else if (!strcasecmp(v->name, "maxlogintries") && !ast_strlen_zero(v->value)) {
    			maxlogintries = atoi(v->value);
    			if (maxlogintries < 0)
    				maxlogintries = 0;
    		} else if (!strcasecmp(v->name, "goodbye") && !ast_strlen_zero(v->value)) {
    			strcpy(agentgoodbye,v->value);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		} else if (!strcasecmp(v->name, "musiconhold")) {
    			strncpy(moh, v->value, sizeof(moh) - 1);
    
    		} else if (!strcasecmp(v->name, "updatecdr")) {
    
    			if (ast_true(v->value))
    				updatecdr = 1;
    			else
    				updatecdr = 0;
    
    		} else if (!strcasecmp(v->name, "recordagentcalls")) {
    			recordagentcalls = ast_true(v->value);
    		} else if (!strcasecmp(v->name, "createlink")) {
    			createlink = ast_true(v->value);
    		} else if (!strcasecmp(v->name, "recordformat")) {
    			strncpy(recordformat, v->value, sizeof(recordformat) - 1);
    			if (!strcasecmp(v->value, "wav49"))
    
    				strncpy(recordformatext, "WAV", sizeof(recordformatext) - 1);
    
    				strncpy(recordformatext, v->value, sizeof(recordformatext) - 1);
    
    		} else if (!strcasecmp(v->name, "urlprefix")) {
    			strncpy(urlprefix, v->value, sizeof(urlprefix) - 2);
    			if (urlprefix[strlen(urlprefix) - 1] != '/')
    
    				strncat(urlprefix, "/", sizeof(urlprefix) - strlen(urlprefix) - 1);
    
    		} else if (!strcasecmp(v->name, "savecallsin")) {
    			if (v->value[0] == '/')
    				strncpy(savecallsin, v->value, sizeof(savecallsin) - 2);
    			else
    				snprintf(savecallsin, sizeof(savecallsin) - 2, "/%s", v->value);