Index: channels/chan_agent.c =================================================================== RCS file: /usr/cvsroot/asterisk/channels/chan_agent.c,v retrieving revision 1.148 diff -u -r1.148 chan_agent.c --- channels/chan_agent.c 7 Sep 2005 20:44:57 -0000 1.148 +++ channels/chan_agent.c 9 Sep 2005 19:15:37 -0000 @@ -10,8 +10,10 @@ * This program is free software, distributed under the terms of * the GNU General Public License */ -/** @file chan_agent.c - * Agents module. +/** + * @file chan_agent.c + * @brief This file is the implementation of Agents modules. + * It is a dynamic module that is loaded by Asterisk. At load time, load_module is run. */ #include @@ -59,48 +61,60 @@ 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"; - -static const char descrip[] = -" 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 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"; - -static const char descrip2[] = -" 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" -"context).\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"; - -static const char descrip3[] = -" AgentMonitorOutgoing([options]):\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"; +/* Indexes for the app-related strings. */ +#define NAME 0 +#define SYNOPSIS 1 +#define DESCRIPTION 2 + +/** + * Metainformation related to the AgentLogin application. + */ +static const char *agent_login_meta[]= { + "AgentLogin", + "Call agent login", + " 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 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"}; + +/** + * Metainformation related to the AgentCallbackLogin application. + */ +static const char *agent_callback_login_meta[]={ + "AgentCallbackLogin", + "Call agent callback login", + " 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" + "context).\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"}; + +/** + * Metainformation related to the AgentMonitorOutgoing application. + */ +static const char *agent_monitor_outgoing_meta[] = { + "AgentMonitorOutgoing", + "Record agent's outgoing call", + " AgentMonitorOutgoing([options]):\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" @@ -188,9 +202,10 @@ char logincallerid[80]; /**< Caller ID they had when they logged in */ struct ast_channel *chan; /**< Channel we use */ struct agent_pvt *next; /**< Next Agent in the linked list. */ + int realtime; /**< Is this a realtime agent ? */ }; -struct agent_pvt *agents = NULL; /**< Holds the list of agents (loaded form agents.conf). */ +static struct agent_pvt *agents = NULL; /**< Holds the list of agents (loaded form agents.conf). */ #define CHECK_FORMATS(ast, p) do { \ if (p->chan) {\ @@ -356,6 +371,7 @@ ast_copy_string(p->moh, moh, sizeof(p->moh)); p->ackcall = ackcall; p->autologoff = autologoff; + p->realtime = 0; /* By default, we are not realtime. */ /* If someone reduces the wrapuptime and reloads, we want it * to change the wrapuptime immediately on all calls */ @@ -378,21 +394,28 @@ return p; } +/** + * Deletes an agent after doing some clean up. + * Further documentation: How safe is this function ? What state should the agent be to be cleaned. + * @param p Agent to be deleted. + * @returns Always 0. + */ static int agent_cleanup(struct agent_pvt *p) { struct ast_channel *chan = p->owner; p->owner = NULL; - chan->tech_pvt = NULL; p->app_sleep_cond = 1; /* Release ownership of the agent to other threads (presumably running the login app). */ ast_mutex_unlock(&p->app_lock); - if (chan) + if (chan){ + chan->tech_pvt = NULL; ast_channel_free(chan); + } if (p->dead) { ast_mutex_destroy(&p->lock); ast_mutex_destroy(&p->app_lock); free(p); - } + } return 0; } @@ -1124,6 +1147,9 @@ return 0; } +/** + * What does this do ? + */ static int check_availability(struct agent_pvt *newlyavailable, int needlock) { struct ast_channel *chan=NULL, *parent=NULL; @@ -1355,7 +1381,14 @@ return 0; } -/*--- action_agents: Manager routine for listing channels */ +/** + * Lists agents and their status to the Manager API. + * It is registered on load_module() and it gets called by the manager backend. + * @param s + * @param m + * @returns + * @sa action_agent_logoff(), action_agent_callback_login(), load_module(). + */ static int action_agents(struct mansession *s, struct message *m) { char *id = astman_get_header(m,"ActionID"); @@ -1491,6 +1524,14 @@ return RESULT_SUCCESS; } +/** + * Sets an agent as no longer logged in in the Manager API. + * It is registered on load_module() and it gets called by the manager backend. + * @param s + * @param m + * @returns + * @sa action_agents(), action_agent_callback_login(), load_module(). + */ static int action_agent_logoff(struct mansession *s, struct message *m) { char *agent = astman_get_header(m, "Agent"); @@ -1626,7 +1667,93 @@ STANDARD_LOCAL_USER; LOCAL_USER_DECL; -/*--- __login_exec: Log in agent application ---*/ + +/** + * Find an agent in the list based on the username. + * + * @param username Username of the agent to find. + * @returns A pointer to the structure of the found agent or null. + */ +static struct agent_pvt *find_agent(const char *username){ + struct agent_pvt *agent = agents; /* Get a pointer to the first agent. */ + + /* Iterate thru agents to find the one. */ + ast_mutex_lock(&agentlock); + while(agent) { + /* Check if the agent is realtime and if this realtime agent still exists on the database.*/ + if(agent->realtime && !ast_load_realtime("agents", "user", agent->agent, NULL)){ + ast_mutex_lock(&agent->lock); + /* The username changed or the agent doesn't exist anymore. */ + struct agent_pvt *next_agent = agent->next; /* Save a pointer to the next agent. */ + agent->dead = 1; /* Mark this agent for destruction. */ + agent_unlink(agent); /* Take it out of the list. */ + agent_cleanup(agent); /* Destruct it. */ + agent = next_agent; /* Make the current, the next one. */ + continue; + } + + /* Compare the usernames and ensure it is not pending. FIXME: shouldn't we also avoid 'dead' agents ? */ + if (!strcmp(agent->agent, username) && !agent->pending){ + /* Is this agent realtime ? */ + if(agent->realtime){ + ast_mutex_lock(&agent->lock); + /* If so, fetch its data from the database to update it.*/ + struct ast_variable *var = ast_load_realtime("agents", "user", username, NULL); + /* Update agent pass and name. */ + while(var){ + if(!strcasecmp(var->name, "pass")) { + ast_copy_string(agent->password, var->value, sizeof(agent->password)); + } else if(!strcasecmp(var->name, "name")) { + ast_copy_string(agent->name, var->value, sizeof(agent->name)); + } + var = var->next; + } + /* TODO: handle pendiness when it is implemented. */ + ast_mutex_unlock(&agent->lock); + } + break; + } + agent = agent->next; /* Get the next agent. */ + } + + if(!agent){ + // If no agent was found on the agents.conf, lets try realtime. + struct ast_variable *var = ast_load_realtime("agents", "user", username, NULL); + if(var){ + char user[AST_MAX_AGENT], pass[AST_MAX_AGENT], name[AST_MAX_AGENT]; + char user_pass_name[AST_MAX_AGENT * 3 + 2]; /* Enough space for the previous three strings and two commas separating them. */ + while(var){ + if(!strcasecmp(var->name, "user")) { + ast_copy_string(user, var->value, sizeof(user)); + } else if(!strcasecmp(var->name, "pass")) { + ast_copy_string(pass, var->value, sizeof(pass)); + } else if(!strcasecmp(var->name, "name")) { + ast_copy_string(name, var->value, sizeof(name)); + } + var = var->next; + } + /* Build a string similar to the one that is gotten from the configuration file. + This is needed because build_agent() expects it, but this shouldn't be consider more than a hack, build_agent()'s API should be fixed. */ + snprintf(user_pass_name, AST_MAX_AGENT * 3 + 2, "%s,%s,%s", user, pass, name); + // TODO: grab pendiness from the table. + agent = add_agent(user_pass_name, 0); + agent->realtime = 1; /* It is a realtime agent */ + } + } + + ast_mutex_unlock(&agentlock); + + return agent; /* Return the agent, if no agent was found, this should be NULL, shouldn't it ? */ +} + +/** + * Log in agent application. + * + * @param chan + * @param data + * @param callbackmode + * @returns + */ static int __login_exec(struct ast_channel *chan, void *data, int callbackmode) { int res=0; @@ -1736,14 +1863,19 @@ while (!res && (max_login_tries==0 || tries < max_login_tries)) { tries++; /* Check for password */ - ast_mutex_lock(&agentlock); + p = find_agent(user); + if(p){ + ast_copy_string(xpass, p->password, sizeof(xpass)); + } +/* FIXME: The following code should go once the previous one is known to work. */ +/* ast_mutex_lock(&agentlock); p = agents; while(p) { if (!strcmp(p->agent, user) && !p->pending) ast_copy_string(xpass, p->password, sizeof(xpass)); p = p->next; } - ast_mutex_unlock(&agentlock); + ast_mutex_unlock(&agentlock); */ if (!res) { if (!ast_strlen_zero(xpass)) res = ast_app_getdata(chan, "agent-pass", pass, sizeof(pass) - 1, 0); @@ -1756,10 +1888,11 @@ ast_log(LOG_NOTICE, "user: %s, pass: %s\n", user, pass); #endif - /* Check again for accuracy */ + /* The following code used to me a while that got thru all the agents once more, checking user and password while we had already checked for user, it didn't make much sense. To see chat code check Asterisk from CVS HEAD as of 2005-09-05. + I (Pupeno) did a simple change to make the while into an if, but the whole code is still clutered with statment that only made sense in the previous scheme. + TODO: Clean this code up. */ ast_mutex_lock(&agentlock); - p = agents; - while(p) { + if(p) { ast_mutex_lock(&p->lock); if (!strcmp(p->agent, user) && !strcmp(p->password, pass) && !p->pending) { @@ -2030,10 +2163,10 @@ break; } ast_mutex_unlock(&p->lock); - p = p->next; - } - if (!p) + } else { +/* if (!p) */ ast_mutex_unlock(&agentlock); + } if (!res && (max_login_tries==0 || tries < max_login_tries)) res = ast_app_getdata(chan, errmsg, user, sizeof(user) - 1, 0); @@ -2080,16 +2213,40 @@ return -1; } +/** + * Called by the AgentLogin application (from the dial plan). + * + * @param chan + * @param data + * @returns + * @sa callback_login_exec(), agentmonitoroutgoing_exec(), load_module(). + */ 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) +/** + * Called by the AgentCallbackLogin application (from the dial plan). + * + * @param chan + * @param data + * @returns + * @sa login_exec(), agentmonitoroutgoing_exec(), load_module(). + */ +static int callback_login_exec(struct ast_channel *chan, void *data) { return __login_exec(chan, data, 1); } +/** + * Sets an agent as logged in by callback in the Manager API. + * It is registered on load_module() and it gets called by the manager backend. + * @param s + * @param m + * @returns + * @sa action_agents(), action_agent_logoff(), load_module(). + */ static int action_agent_callback_login(struct mansession *s, struct message *m) { char *agent = astman_get_header(m, "Agent"); @@ -2165,6 +2322,15 @@ return 0; } + +/** + * Called by the AgentMonitorOutgoing application (from the dial plan). + * + * @param chan + * @param data + * @returns + * @sa login_exec(), callback_login_exec(), load_module(). + */ static int agentmonitoroutgoing_exec(struct ast_channel *chan, void *data) { int exitifnoagentid = 0; @@ -2221,9 +2387,9 @@ return 0; } -/* Dump AgentCallbackLogin agents to the database for persistence +/** + * Dump AgentCallbackLogin agents to the database for persistence */ - static void dump_agents(void) { struct agent_pvt *cur_agent = NULL; @@ -2366,17 +2532,42 @@ ast_log(LOG_ERROR, "Unable to register channel class %s\n", channeltype); return -1; } + /* Dialplan applications */ - ast_register_application(app, login_exec, synopsis, descrip); - ast_register_application(app2, callback_exec, synopsis2, descrip2); - ast_register_application(app3, agentmonitoroutgoing_exec, synopsis3, descrip3); - /* Manager commands */ - ast_manager_register2("Agents", EVENT_FLAG_AGENT, action_agents, "Lists agents and their status", mandescr_agents); - ast_manager_register2("AgentLogoff", EVENT_FLAG_AGENT, action_agent_logoff, "Sets an agent as no longer logged in", mandescr_agent_logoff); - ast_manager_register2("AgentCallbackLogin", EVENT_FLAG_AGENT, action_agent_callback_login, "Sets an agent as logged in by callback", mandescr_agent_callback_login); + ast_register_application(agent_login_meta[NAME], + login_exec, + agent_login_meta[SYNOPSIS], + agent_login_meta[DESCRIPTION]); + ast_register_application(agent_callback_login_meta[NAME], + callback_login_exec, + agent_callback_login_meta[SYNOPSIS], + agent_callback_login_meta[DESCRIPTION]); + ast_register_application(agent_monitor_outgoing_meta[NAME], + agentmonitoroutgoing_exec, + agent_monitor_outgoing_meta[SYNOPSIS], + agent_monitor_outgoing_meta[DESCRIPTION]); + + /* Manager commands */ + ast_manager_register2("Agents", + EVENT_FLAG_AGENT, + action_agents, + "Lists agents and their status", + mandescr_agents); + ast_manager_register2("AgentLogoff", + EVENT_FLAG_AGENT, + action_agent_logoff, + "Sets an agent as no longer logged in", + mandescr_agent_logoff); + ast_manager_register2("AgentCallbackLogin", + EVENT_FLAG_AGENT, + action_agent_callback_login, + "Sets an agent as logged in by callback", + mandescr_agent_callback_login); + /* CLI Application */ ast_cli_register(&cli_show_agents); ast_cli_register(&cli_agent_logoff); + /* Read in the config */ read_agent_config(); if (persistent_agents) @@ -2399,14 +2590,17 @@ /* Unregister CLI application */ ast_cli_unregister(&cli_show_agents); ast_cli_unregister(&cli_agent_logoff); + /* Unregister dialplan applications */ - ast_unregister_application(app); - ast_unregister_application(app2); - ast_unregister_application(app3); + ast_unregister_application(agent_login_meta[NAME]); + ast_unregister_application(agent_callback_login_meta[NAME]); + ast_unregister_application(agent_monitor_outgoing_meta[NAME]); + /* Unregister manager command */ ast_manager_unregister("Agents"); ast_manager_unregister("AgentLogoff"); ast_manager_unregister("AgentCallbackLogin"); + /* Unregister channel */ ast_channel_unregister(&agent_tech); if (!ast_mutex_lock(&agentlock)) {