Skip to content
Snippets Groups Projects
app_agent_pool.c 76.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	if (ast_bridge_join(caller_bridge, chan, NULL, &caller_features, NULL,
    		AST_BRIDGE_JOIN_PASS_REFERENCE)) {
    
    		caller_abort_agent(agent);
    
    		ast_verb(3, "Agent '%s': Caller %s failed to join the bridge.\n",
    			agent->username, ast_channel_name(chan));
    		pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "ERROR");
    
    	}
    	ast_bridge_features_cleanup(&caller_features);
    
    
    	/* Determine if we need to continue in the dialplan after the bridge. */
    	ast_channel_lock(chan);
    	if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
    		/*
    		 * The bridge was broken for a hangup that isn't real.
    		 * Don't run the h extension, because the channel isn't
    		 * really hung up.  This should really only happen with
    		 * AST_SOFTHANGUP_ASYNCGOTO.
    		 */
    		res = 0;
    	} else {
    		res = ast_check_hangup(chan)
    			|| ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE)
    			|| ast_strlen_zero(pbx_builtin_getvar_helper(chan, "AGENT_STATUS"));
    	}
    	ast_channel_unlock(chan);
    
    	return res ? -1 : 0;
    
    }
    
    /*!
     * \internal
     * \brief Get agent config values from the login channel.
     * \since 12.0.0
     *
     * \param agent What to setup channel config values on.
     * \param chan Channel logging in as an agent.
     *
     * \return Nothing
     */
    static void agent_login_channel_config(struct agent_pvt *agent, struct ast_channel *chan)
    {
    	struct ast_flags opts = { 0 };
    	struct ast_party_connected_line connected;
    	unsigned int override_ack_call = 0;
    	unsigned int override_auto_logoff = 0;
    	unsigned int override_wrapup_time = 0;
    	const char *override_dtmf_accept = NULL;
    	const char *var;
    
    	ast_party_connected_line_init(&connected);
    
    	/* Get config values from channel. */
    	ast_channel_lock(chan);
    	ast_party_connected_line_copy(&connected, ast_channel_connected(chan));
    
    	var = pbx_builtin_getvar_helper(chan, "AGENTACKCALL");
    	if (!ast_strlen_zero(var)) {
    		override_ack_call = ast_true(var) ? 1 : 0;
    		ast_set_flag(&opts, AGENT_FLAG_ACK_CALL);
    	}
    
    	var = pbx_builtin_getvar_helper(chan, "AGENTACCEPTDTMF");
    	if (!ast_strlen_zero(var)) {
    		override_dtmf_accept = ast_strdupa(var);
    		ast_set_flag(&opts, AGENT_FLAG_DTMF_ACCEPT);
    	}
    
    	var = pbx_builtin_getvar_helper(chan, "AGENTAUTOLOGOFF");
    	if (!ast_strlen_zero(var)) {
    		if (sscanf(var, "%u", &override_auto_logoff) == 1) {
    			ast_set_flag(&opts, AGENT_FLAG_AUTO_LOGOFF);
    		}
    	}
    
    	var = pbx_builtin_getvar_helper(chan, "AGENTWRAPUPTIME");
    	if (!ast_strlen_zero(var)) {
    		if (sscanf(var, "%u", &override_wrapup_time) == 1) {
    			ast_set_flag(&opts, AGENT_FLAG_WRAPUP_TIME);
    		}
    	}
    	ast_channel_unlock(chan);
    
    	/* Set config values on agent. */
    	agent_lock(agent);
    	ast_party_connected_line_free(&agent->waiting_colp);
    	agent->waiting_colp = connected;
    
    	ast_string_field_set(agent, override_dtmf_accept, override_dtmf_accept);
    	ast_copy_flags(agent, &opts, AST_FLAGS_ALL);
    	agent->override_auto_logoff = override_auto_logoff;
    	agent->override_wrapup_time = override_wrapup_time;
    	agent->override_ack_call = override_ack_call;
    	agent_unlock(agent);
    }
    
    enum AGENT_LOGIN_OPT_FLAGS {
    	OPT_SILENT = (1 << 0),
    };
    AST_APP_OPTIONS(agent_login_opts, BEGIN_OPTIONS
    	AST_APP_OPTION('s', OPT_SILENT),
    END_OPTIONS);
    
    /*!
     * \brief Dialplan AgentLogin application to log in an agent.
     *
     * \param chan Channel attempting to login as an agent.
     * \param data Application parameters
     *
     * \retval 0 To continue in dialplan.
     * \retval -1 To hangup.
     */
    static int agent_login_exec(struct ast_channel *chan, const char *data)
    {
    	char *parse;
    	struct ast_flags opts;
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(agent_id);
    		AST_APP_ARG(options);
    		AST_APP_ARG(other);		/* Any remaining unused arguments */
    	);
    
    	RAII_VAR(struct agent_pvt *, agent, NULL, ao2_cleanup);
    
    	if (bridge_agent_hold_deferred_create()) {
    		return -1;
    	}
    
    	if (ast_channel_state(chan) != AST_STATE_UP && ast_answer(chan)) {
    		return -1;
    	}
    
    	parse = ast_strdupa(data);
    	AST_STANDARD_APP_ARGS(args, parse);
    
    	if (ast_strlen_zero(args.agent_id)) {
    		ast_log(LOG_WARNING, "AgentLogin requires an AgentId\n");
    		return -1;
    	}
    
    	if (ast_app_parse_options(agent_login_opts, &opts, NULL, args.options)) {
    		/* General invalid option syntax. */
    		return -1;
    	}
    
    	/* Find the agent. */
    	agent = ao2_find(agents, args.agent_id, OBJ_KEY);
    	if (!agent) {
    		ast_verb(3, "Agent '%s' does not exist.\n", args.agent_id);
    		pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "INVALID");
    		return 0;
    	}
    
    	/* Has someone already logged in as this agent already? */
    	agent_lock(agent);
    	if (agent->logged) {
    		agent_unlock(agent);
    		ast_verb(3, "Agent '%s' already logged in.\n", agent->username);
    		pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "ALREADY_LOGGED_IN");
    		return 0;
    	}
    	agent->logged = ast_channel_ref(chan);
    	agent->last_disconnect = ast_tvnow();
    	time(&agent->login_start);
    
    	agent_unlock(agent);
    
    	agent_login_channel_config(agent, chan);
    
    
    	if (!ast_test_flag(&opts, OPT_SILENT)) {
    		ast_stream_and_wait(chan, "agent-loginok", AST_DIGIT_NONE);
    
    	}
    
    	ast_verb(2, "Agent '%s' logged in (format %s/%s)\n", agent->username,
    
    		ast_format_get_name(ast_channel_readformat(chan)),
    		ast_format_get_name(ast_channel_writeformat(chan)));
    
    	send_agent_login(chan, agent->username);
    
    2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533
    
    	agent_run(agent, chan);
    	return -1;
    }
    
    static int agent_function_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
    {
    	char *parse;
    	struct agent_pvt *agent;
    	struct ast_channel *logged;
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(agentid);
    		AST_APP_ARG(item);
    	);
    
    	buf[0] = '\0';
    
    	parse = ast_strdupa(data ?: "");
    	AST_NONSTANDARD_APP_ARGS(args, parse, ':');
    
    	if (ast_strlen_zero(args.agentid)) {
    		ast_log(LOG_WARNING, "The AGENT function requires an argument - agentid!\n");
    		return -1;
    	}
    	if (!args.item) {
    		args.item = "status";
    	}
    
    	agent = ao2_find(agents, args.agentid, OBJ_KEY);
    	if (!agent) {
    		ast_log(LOG_WARNING, "Agent '%s' not found!\n", args.agentid);
    		return -1;
    	}
    
    	agent_lock(agent);
    	if (!strcasecmp(args.item, "status")) {
    		const char *status;
    
    		if (agent->logged) {
    			status = "LOGGEDIN";
    		} else {
    			status = "LOGGEDOUT";
    		}
    		ast_copy_string(buf, status, len);
    	} else if (!strcasecmp(args.item, "name")) {
    		ast_copy_string(buf, agent->cfg->full_name, len);
    	} else if (!strcasecmp(args.item, "mohclass")) {
    		ast_copy_string(buf, agent->cfg->moh, len);
    	} else if (!strcasecmp(args.item, "channel")) {
    		logged = agent_lock_logged(agent);
    		if (logged) {
    			char *pos;
    
    			ast_copy_string(buf, ast_channel_name(logged), len);
    			ast_channel_unlock(logged);
    			ast_channel_unref(logged);
    
    			pos = strrchr(buf, '-');
    			if (pos) {
    				*pos = '\0';
    			}
    		}
    	} else if (!strcasecmp(args.item, "fullchannel")) {
    		logged = agent_lock_logged(agent);
    		if (logged) {
    			ast_copy_string(buf, ast_channel_name(logged), len);
    			ast_channel_unlock(logged);
    			ast_channel_unref(logged);
    		}
    	}
    	agent_unlock(agent);
    	ao2_ref(agent, -1);
    
    	return 0;
    }
    
    static struct ast_custom_function agent_function = {
    	.name = "AGENT",
    	.read = agent_function_read,
    };
    
    struct agent_complete {
    	/*! Nth match to return. */
    	int state;
    	/*! Which match currently on. */
    	int which;
    };
    
    static int complete_agent_search(void *obj, void *arg, void *data, int flags)
    {
    	struct agent_complete *search = data;
    
    	if (++search->which > search->state) {
    		return CMP_MATCH;
    	}
    	return 0;
    }
    
    static char *complete_agent(const char *word, int state)
    {
    	char *ret;
    	struct agent_pvt *agent;
    	struct agent_complete search = {
    		.state = state,
    	};
    
    	agent = ao2_callback_data(agents, ast_strlen_zero(word) ? 0 : OBJ_PARTIAL_KEY,
    		complete_agent_search, (char *) word, &search);
    	if (!agent) {
    		return NULL;
    	}
    	ret = ast_strdup(agent->username);
    	ao2_ref(agent, -1);
    	return ret;
    }
    
    static int complete_agent_logoff_search(void *obj, void *arg, void *data, int flags)
    {
    	struct agent_pvt *agent = obj;
    	struct agent_complete *search = data;
    
    	if (!agent->logged) {
    		return 0;
    	}
    	if (++search->which > search->state) {
    		return CMP_MATCH;
    	}
    	return 0;
    }
    
    static char *complete_agent_logoff(const char *word, int state)
    {
    	char *ret;
    	struct agent_pvt *agent;
    	struct agent_complete search = {
    		.state = state,
    	};
    
    	agent = ao2_callback_data(agents, ast_strlen_zero(word) ? 0 : OBJ_PARTIAL_KEY,
    		complete_agent_logoff_search, (char *) word, &search);
    	if (!agent) {
    		return NULL;
    	}
    	ret = ast_strdup(agent->username);
    	ao2_ref(agent, -1);
    	return ret;
    }
    
    static void agent_show_requested(struct ast_cli_args *a, int online_only)
    {
    #define FORMAT_HDR "%-8s %-20s %-11s %-30s %s\n"
    #define FORMAT_ROW "%-8s %-20s %-11s %-30s %s\n"
    
    	struct ao2_iterator iter;
    	struct agent_pvt *agent;
    	struct ast_str *out = ast_str_alloca(512);
    	unsigned int agents_total = 0;
    	unsigned int agents_logged_in = 0;
    	unsigned int agents_talking = 0;
    
    	ast_cli(a->fd, FORMAT_HDR, "Agent-ID", "Name", "State", "Channel", "Talking with");
    	iter = ao2_iterator_init(agents, 0);
    	for (; (agent = ao2_iterator_next(&iter)); ao2_ref(agent, -1)) {
    		struct ast_channel *logged;
    
    		++agents_total;
    
    		agent_lock(agent);
    		logged = agent_lock_logged(agent);
    		if (logged) {
    			const char *talking_with;
    
    			++agents_logged_in;
    
    			talking_with = pbx_builtin_getvar_helper(logged, "BRIDGEPEER");
    			if (!ast_strlen_zero(talking_with)) {
    				++agents_talking;
    			} else {
    				talking_with = "";
    			}
    			ast_str_set(&out, 0, FORMAT_ROW, agent->username, agent->cfg->full_name,
    				ast_devstate_str(agent->devstate), ast_channel_name(logged), talking_with);
    			ast_channel_unlock(logged);
    			ast_channel_unref(logged);
    		} else {
    			ast_str_set(&out, 0, FORMAT_ROW, agent->username, agent->cfg->full_name,
    				ast_devstate_str(agent->devstate), "", "");
    		}
    		agent_unlock(agent);
    
    		if (!online_only || logged) {
    			ast_cli(a->fd, "%s", ast_str_buffer(out));
    		}
    	}
    	ao2_iterator_destroy(&iter);
    
    	ast_cli(a->fd, "\nDefined agents: %u, Logged in: %u, Talking: %u\n",
    		agents_total, agents_logged_in, agents_talking);
    
    #undef FORMAT_HDR
    #undef FORMAT_ROW
    }
    
    static char *agent_handle_show_online(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "agent show online";
    		e->usage =
    			"Usage: agent show online\n"
    			"       Provides summary information for logged in agents.\n";
    		return NULL;
    	case CLI_GENERATE:
    		return NULL;
    	}
    
    	if (a->argc != 3) {
    		return CLI_SHOWUSAGE;
    	}
    
    	agent_show_requested(a, 1);
    
    	return CLI_SUCCESS;
    }
    
    static char *agent_handle_show_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "agent show all";
    		e->usage =
    			"Usage: agent show all\n"
    			"       Provides summary information for all agents.\n";
    		return NULL;
    	case CLI_GENERATE:
    		return NULL;
    	}
    
    	if (a->argc != 3) {
    		return CLI_SHOWUSAGE;
    	}
    
    	agent_show_requested(a, 0);
    
    	return CLI_SUCCESS;
    }
    
    static char *agent_handle_show_specific(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	struct agent_pvt *agent;
    	struct ast_channel *logged;
    	struct ast_str *out = ast_str_alloca(4096);
    
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "agent show";
    		e->usage =
    			"Usage: agent show <agent-id>\n"
    			"       Show information about the <agent-id> agent\n";
    		return NULL;
    	case CLI_GENERATE:
    		if (a->pos == 2) {
    			return complete_agent(a->word, a->n);
    		}
    		return NULL;
    	}
    
    	if (a->argc != 3) {
    		return CLI_SHOWUSAGE;
    	}
    
    	agent = ao2_find(agents, a->argv[2], OBJ_KEY);
    	if (!agent) {
    		ast_cli(a->fd, "Agent '%s' not found\n", a->argv[2]);
    		return CLI_SUCCESS;
    	}
    
    	agent_lock(agent);
    	logged = agent_lock_logged(agent);
    	ast_str_set(&out, 0, "Id: %s\n", agent->username);
    	ast_str_append(&out, 0, "Name: %s\n", agent->cfg->full_name);
    	ast_str_append(&out, 0, "Beep: %s\n", agent->cfg->beep_sound);
    	ast_str_append(&out, 0, "MOH: %s\n", agent->cfg->moh);
    	ast_str_append(&out, 0, "RecordCalls: %s\n", AST_CLI_YESNO(agent->cfg->record_agent_calls));
    	ast_str_append(&out, 0, "State: %s\n", ast_devstate_str(agent->devstate));
    	if (logged) {
    		const char *talking_with;
    
    		ast_str_append(&out, 0, "LoggedInChannel: %s\n", ast_channel_name(logged));
    		ast_str_append(&out, 0, "LoggedInTime: %ld\n", (long) agent->login_start);
    		talking_with = pbx_builtin_getvar_helper(logged, "BRIDGEPEER");
    		if (!ast_strlen_zero(talking_with)) {
    			ast_str_append(&out, 0, "TalkingWith: %s\n", talking_with);
    			ast_str_append(&out, 0, "CallStarted: %ld\n", (long) agent->call_start);
    		}
    		ast_channel_unlock(logged);
    		ast_channel_unref(logged);
    	}
    	agent_unlock(agent);
    	ao2_ref(agent, -1);
    
    	ast_cli(a->fd, "%s", ast_str_buffer(out));
    
    	return CLI_SUCCESS;
    }
    
    static char *agent_handle_logoff_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	switch (cmd) {
    	case CLI_INIT:
    		e->command = "agent logoff";
    		e->usage =
    			"Usage: agent logoff <agent-id> [soft]\n"
    			"       Sets an agent as no longer logged in.\n"
    			"       If 'soft' is specified, do not hangup existing calls.\n";
    		return NULL;
    	case CLI_GENERATE:
    		if (a->pos == 2) {
    			return complete_agent_logoff(a->word, a->n);
    		} else if (a->pos == 3 && a->n == 0
    			&& (ast_strlen_zero(a->word)
    				|| !strncasecmp("soft", a->word, strlen(a->word)))) {
    			return ast_strdup("soft");
    		}
    		return NULL;
    	}
    
    	if (a->argc < 3 || 4 < a->argc) {
    		return CLI_SHOWUSAGE;
    	}
    	if (a->argc == 4 && strcasecmp(a->argv[3], "soft")) {
    		return CLI_SHOWUSAGE;
    	}
    
    	if (!agent_logoff_request(a->argv[2], a->argc == 4)) {
    		ast_cli(a->fd, "Logging out %s\n", a->argv[2]);
    	}
    
    	return CLI_SUCCESS;
    }
    
    static struct ast_cli_entry cli_agents[] = {
    	AST_CLI_DEFINE(agent_handle_show_online, "Show status of online agents"),
    	AST_CLI_DEFINE(agent_handle_show_all, "Show status of all agents"),
    	AST_CLI_DEFINE(agent_handle_show_specific, "Show information about an agent"),
    	AST_CLI_DEFINE(agent_handle_logoff_cmd, "Sets an agent offline"),
    };
    
    static int action_agents(struct mansession *s, const struct message *m)
    {
    	const char *id = astman_get_header(m, "ActionID");
    	char id_text[AST_MAX_BUF];
    	struct ao2_iterator iter;
    	struct agent_pvt *agent;
    	struct ast_str *out = ast_str_alloca(4096);
    
    
    	if (!ast_strlen_zero(id)) {
    		snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", id);
    	} else {
    		id_text[0] = '\0';
    	}
    
    	astman_send_listack(s, m, "Agents will follow", "start");
    
    
    	iter = ao2_iterator_init(agents, 0);
    	for (; (agent = ao2_iterator_next(&iter)); ao2_ref(agent, -1)) {
    		struct ast_channel *logged;
    
    		agent_lock(agent);
    		logged = agent_lock_logged(agent);
    
    		/*
    		 * Status Values:
    		 * AGENT_LOGGEDOFF - Agent isn't logged in
    		 * AGENT_IDLE      - Agent is logged in, and waiting for call
    		 * AGENT_ONCALL    - Agent is logged in, and on a call
    		 * AGENT_UNKNOWN   - Don't know anything about agent. Shouldn't ever get this.
    		 */
    		ast_str_set(&out, 0, "Agent: %s\r\n", agent->username);
    		ast_str_append(&out, 0, "Name: %s\r\n", agent->cfg->full_name);
    
    		if (logged) {
    			const char *talking_to_chan;
    			struct ast_str *logged_headers;
    			RAII_VAR(struct ast_channel_snapshot *, logged_snapshot, ast_channel_snapshot_create(logged), ao2_cleanup);
    
    			if (!logged_snapshot
    				|| !(logged_headers =
    					 ast_manager_build_channel_state_string(logged_snapshot))) {
    				ast_channel_unlock(logged);
    				ast_channel_unref(logged);
    				agent_unlock(agent);
    				continue;
    			}
    
    			talking_to_chan = pbx_builtin_getvar_helper(logged, "BRIDGEPEER");
    			if (!ast_strlen_zero(talking_to_chan)) {
    				ast_str_append(&out, 0, "Status: %s\r\n", "AGENT_ONCALL");
    				ast_str_append(&out, 0, "TalkingToChan: %s\r\n", talking_to_chan);
    				ast_str_append(&out, 0, "CallStarted: %ld\n", (long) agent->call_start);
    			} else {
    				ast_str_append(&out, 0, "Status: %s\r\n", "AGENT_IDLE");
    			}
    			ast_str_append(&out, 0, "LoggedInTime: %ld\r\n", (long) agent->login_start);
    			ast_str_append(&out, 0, "%s", ast_str_buffer(logged_headers));
    			ast_channel_unlock(logged);
    			ast_channel_unref(logged);
    			ast_free(logged_headers);
    		} else {
    			ast_str_append(&out, 0, "Status: %s\r\n", "AGENT_LOGGEDOFF");
    		}
    
    		agent_unlock(agent);
    
    		astman_append(s, "Event: Agents\r\n"
    			"%s%s\r\n",
    			ast_str_buffer(out), id_text);
    
    	}
    	ao2_iterator_destroy(&iter);
    
    
    	astman_send_list_complete_start(s, m, "AgentsComplete", num_agents);
    	astman_send_list_complete_end(s);
    
    	return 0;
    }
    
    static int action_agent_logoff(struct mansession *s, const struct message *m)
    {
    	const char *agent = astman_get_header(m, "Agent");
    	const char *soft_s = astman_get_header(m, "Soft"); /* "true" is don't hangup */
    
    	if (ast_strlen_zero(agent)) {
    		astman_send_error(s, m, "No agent specified");
    		return 0;
    	}
    
    	if (!agent_logoff_request(agent, ast_true(soft_s))) {
    		astman_send_ack(s, m, "Agent logged out");
    	} else {
    		astman_send_error(s, m, "No such agent");
    	}
    
    	return 0;
    }
    
    static int unload_module(void)
    {
    	struct ast_bridge *holding;
    
    	/* Unregister dialplan applications */
    	ast_unregister_application(app_agent_login);
    	ast_unregister_application(app_agent_request);
    
    	/* Unregister dialplan functions */
    	ast_custom_function_unregister(&agent_function);
    
    	/* Unregister manager command */
    	ast_manager_unregister("Agents");
    	ast_manager_unregister("AgentLogoff");
    
    	/* Unregister CLI commands */
    	ast_cli_unregister_multiple(cli_agents, ARRAY_LEN(cli_agents));
    
    	ast_devstate_prov_del("Agent");
    
    	/* Destroy agent holding bridge. */
    	holding = ao2_global_obj_replace(agent_holding, NULL);
    	if (holding) {
    
    	agents = NULL;
    	return 0;
    }
    
    static int load_module(void)
    {
    	int res = 0;
    
    	agents = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_MUTEX,
    		AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, agent_pvt_sort_cmp, agent_pvt_cmp);
    	if (!agents) {
    
    		return AST_MODULE_LOAD_DECLINE;
    
    	}
    
    	/* Init agent holding bridge v_table. */
    
    	bridge_init_agent_hold();
    
    
    	/* Setup to provide Agent:agent-id device state. */
    	res |= ast_devstate_prov_add("Agent", agent_pvt_devstate_get);
    
    	/* CLI Commands */
    	res |= ast_cli_register_multiple(cli_agents, ARRAY_LEN(cli_agents));
    
    	/* Manager commands */
    	res |= ast_manager_register_xml("Agents", EVENT_FLAG_AGENT, action_agents);
    	res |= ast_manager_register_xml("AgentLogoff", EVENT_FLAG_AGENT, action_agent_logoff);
    
    	/* Dialplan Functions */
    	res |= ast_custom_function_register(&agent_function);
    
    	/* Dialplan applications */
    	res |= ast_register_application_xml(app_agent_login, agent_login_exec);
    	res |= ast_register_application_xml(app_agent_request, agent_request_exec);
    
    	if (res) {
    
    		ast_log(LOG_ERROR, "Unable to register application. Not loading module.\n");
    
    		unload_module();
    
    		return AST_MODULE_LOAD_DECLINE;
    
    
    	if (load_config()) {
    		ast_log(LOG_ERROR, "Unable to load config. Not loading module.\n");
    		unload_module();
    		return AST_MODULE_LOAD_DECLINE;
    	}
    
    
    	return AST_MODULE_LOAD_SUCCESS;
    }
    
    static int reload(void)
    {
    	if (aco_process_config(&cfg_info, 1) == ACO_PROCESS_ERROR) {
    		/* Just keep the config we already have in place. */
    		return -1;
    	}
    	return 0;
    }
    
    AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Call center agent pool applications",
    
    	.load = load_module,
    	.unload = unload_module,
    	.reload = reload,
    	.load_pri = AST_MODPRI_DEVSTATE_PROVIDER,
    );