diff options
author | markster <markster@f38db490-d61c-443f-a65b-d21fe96a405b> | 2002-09-12 18:32:42 +0000 |
---|---|---|
committer | markster <markster@f38db490-d61c-443f-a65b-d21fe96a405b> | 2002-09-12 18:32:42 +0000 |
commit | 39ec885c0e48096095a8a3beecb2b59c5ed76fc7 (patch) | |
tree | 13e613480960be9a46402cd1706f63c7a3ab3f7f | |
parent | 4adedfc0b7d2e76a4b96438dc5ce2d8659ac134d (diff) |
Version 0.2.0 from FTP
git-svn-id: http://svn.digium.com/svn/asterisk/trunk@526 f38db490-d61c-443f-a65b-d21fe96a405b
-rwxr-xr-x | manager.c | 619 |
1 files changed, 619 insertions, 0 deletions
diff --git a/manager.c b/manager.c new file mode 100755 index 000000000..365fb32b5 --- /dev/null +++ b/manager.c @@ -0,0 +1,619 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * Channel Management and more + * + * Copyright (C) 1999, Mark Spencer + * + * Mark Spencer <markster@linux-support.net> + * + * This program is free software, distributed under the terms of + * the GNU General Public License + */ + +#include <stdio.h> +#include <stdlib.h> +#include <pthread.h> +#include <string.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <signal.h> +#include <errno.h> +#include <unistd.h> +#include <asterisk/channel.h> +#include <asterisk/file.h> +#include <asterisk/manager.h> +#include <asterisk/config.h> +#include <asterisk/lock.h> +#include <asterisk/logger.h> +#include <asterisk/options.h> +#include <asterisk/cli.h> +#include <asterisk/pbx.h> + +static int enabled = 0; +static int portno = DEFAULT_MANAGER_PORT; +static int asock = -1; +static pthread_t t; +static pthread_mutex_t sessionlock = AST_MUTEX_INITIALIZER; + +static struct permalias { + int num; + char *label; +} perms[] = { + { EVENT_FLAG_SYSTEM, "system" }, + { EVENT_FLAG_CALL, "call" }, + { EVENT_FLAG_LOG, "log" }, + { EVENT_FLAG_VERBOSE, "verbose" }, + { EVENT_FLAG_COMMAND, "command" }, + { EVENT_FLAG_AGENT, "agent" }, + { -1, "all" }, +}; + +#define MAX_HEADERS 80 +#define MAX_LEN 256 + +static struct mansession { + pthread_t t; + pthread_mutex_t lock; + struct sockaddr_in sin; + int fd; + char username[80]; + int authenticated; + int readperm; + int writeperm; + char inbuf[MAX_LEN]; + int inlen; + + struct mansession *next; +} *sessions = NULL; + + +struct message { + int hdrcount; + char headers[MAX_HEADERS][MAX_LEN]; +}; + +static void destroy_session(struct mansession *s) +{ + struct mansession *cur, *prev = NULL; + ast_pthread_mutex_lock(&sessionlock); + cur = sessions; + while(cur) { + if (cur == s) + break; + prev = cur; + cur = cur->next; + } + if (cur) { + if (prev) + prev->next = cur->next; + else + sessions = cur->next; + if (s->fd > -1) + close(s->fd); + free(s); + } else + ast_log(LOG_WARNING, "Trying to delete non-existant session %p?\n", s); + ast_pthread_mutex_unlock(&sessionlock); + +} + +static char *get_header(struct message *m, char *var) +{ + char cmp[80]; + int x; + snprintf(cmp, sizeof(cmp), "%s: ", var); + for (x=0;x<m->hdrcount;x++) + if (!strncasecmp(cmp, m->headers[x], strlen(cmp))) + return m->headers[x] + strlen(cmp); + return ""; +} + +static void send_error(struct mansession *s, char *error) +{ + ast_pthread_mutex_lock(&s->lock); + ast_cli(s->fd, "Response: Error\r\n"); + ast_cli(s->fd, "Message: %s\r\n\r\n", error); + ast_pthread_mutex_unlock(&s->lock); +} + +static void send_response(struct mansession *s, char *resp, char *msg) +{ + ast_pthread_mutex_lock(&s->lock); + ast_cli(s->fd, "Response: %s\r\n", resp); + if (msg) + ast_cli(s->fd, "Message: %s\r\n\r\n", msg); + else + ast_cli(s->fd, "\r\n"); + ast_pthread_mutex_unlock(&s->lock); +} + +static void send_ack(struct mansession *s, char *msg) +{ + send_response(s, "Success", msg); +} + +static int get_perm(char *instr) +{ + char tmp[256]; + char *c; + int x; + int ret = 0; + strncpy(tmp, instr, sizeof(tmp) - 1); + c = strtok(tmp, ","); + while(c) { + for (x=0;x<sizeof(perms) / sizeof(perms[0]);x++) { + if (!strcasecmp(perms[x].label, c)) + ret |= perms[x].num; + } + c = strtok(NULL, ","); + } + return ret; +} + +static int authenticate(struct mansession *s, struct message *m) +{ + struct ast_config *cfg; + char *cat; + char *user = get_header(m, "Username"); + char *pass = get_header(m, "Secret"); + cfg = ast_load("manager.conf"); + if (!cfg) + return -1; + cat = ast_category_browse(cfg, NULL); + while(cat) { + if (strcasecmp(cat, "general")) { + /* This is a user */ + if (!strcasecmp(cat, user)) { + char *password = ast_variable_retrieve(cfg, cat, "secret"); + if (password && !strcasecmp(password, pass)) { + break; + } else { + ast_log(LOG_NOTICE, "%s failed to authenticate as '%s'\n", inet_ntoa(s->sin.sin_addr), user); + ast_destroy(cfg); + return -1; + } + } + } + cat = ast_category_browse(cfg, cat); + } + if (cat) { + strncpy(s->username, cat, sizeof(s->username) - 1); + s->readperm = get_perm(ast_variable_retrieve(cfg, cat, "read")); + s->writeperm = get_perm(ast_variable_retrieve(cfg, cat, "write")); + ast_destroy(cfg); + return 0; + } + ast_log(LOG_NOTICE, "%s tried to authenticate with non-existant user '%s'\n", inet_ntoa(s->sin.sin_addr), user); + ast_destroy(cfg); + return -1; +} + +static int action_ping(struct mansession *s, struct message *m) +{ + send_response(s, "Pong", NULL); + return 0; +} + +static int action_logoff(struct mansession *s, struct message *m) +{ + send_response(s, "Goodbye", "Thanks for all the fish."); + return -1; +} + +static int action_hangup(struct mansession *s, struct message *m) +{ + struct ast_channel *c = NULL; + char *name = get_header(m, "Channel"); + if (!strlen(name)) { + send_error(s, "No channel specified"); + return 0; + } + c = ast_channel_walk(NULL); + while(c) { + if (!strcasecmp(c->name, name)) { + break; + } + c = ast_channel_walk(c); + } + if (!c) { + send_error(s, "No such channel"); + return 0; + } + ast_softhangup(c, AST_SOFTHANGUP_EXPLICIT); + send_ack(s, "Channel Hungup"); + return 0; +} + +static int action_status(struct mansession *s, struct message *m) +{ + struct ast_channel *c; + char bridge[256]; + send_ack(s, "Channel status will follow"); + c = ast_channel_walk(NULL); + while(c) { + if (c->bridge) + snprintf(bridge, sizeof(bridge), "Link: %s\r\n", c->bridge->name); + else + strcpy(bridge, ""); + if (c->pbx) { + ast_cli(s->fd, + "Event: Status\r\n" + "Channel: %s\r\n" + "CallerID: %s\r\n" + "State: %s\r\n" + "Context: %s\r\n" + "Extension: %s\r\n" + "Priority: %d\r\n" + "%s" + "\r\n", + c->name, c->callerid ? c->callerid : "<unknown>", + ast_state2str(c->_state), c->context, + c->exten, c->priority, bridge); + } else { + ast_cli(s->fd, + "Event: Status\r\n" + "Channel: %s\r\n" + "CallerID: %s\r\n" + "State: %s\r\n" + "%s" + "\r\n", + c->name, c->callerid ? c->callerid : "<unknown>", + ast_state2str(c->_state), bridge); + } + c = ast_channel_walk(c); + } + return 0; +} + +static int action_redirect(struct mansession *s, struct message *m) +{ + char *name = get_header(m, "Channel"); + char *name2 = get_header(m, "ExtraChannel"); + char *exten = get_header(m, "Exten"); + char *context = get_header(m, "Context"); + char *priority = get_header(m, "Priority"); + int pi = 0; + int res; + if (!name || !strlen(name)) { + send_error(s, "Channel not specified"); + return 0; + } + if (strlen(priority) && (sscanf(priority, "%d", &pi) != 1)) { + send_error(s, "Invalid priority\n"); + return 0; + } + res = ast_async_goto_by_name(name, context, exten, pi); + if (!res) { + if (strlen(name2)) { + res = ast_async_goto_by_name(name2, context, exten, pi); + if (!res) + send_ack(s, "Dual Redirect successful"); + else + send_error(s, "Secondary redirect failed"); + } else + send_ack(s, "Redirect successful"); + } else + send_error(s, "Redirect failed"); + return 0; +} + +static int action_originate(struct mansession *s, struct message *m) +{ + char *name = get_header(m, "Channel"); + char *exten = get_header(m, "Exten"); + char *context = get_header(m, "Context"); + char *priority = get_header(m, "Priority"); + char *timeout = get_header(m, "Timeout"); + char *tech, *data; + int pi = 0; + int res; + int to = 30000; + int reason = 0; + char tmp[256]; + if (!name) { + send_error(s, "Channel not specified"); + return 0; + } + if (strlen(priority) && (sscanf(priority, "%d", &pi) != 1)) { + send_error(s, "Invalid priority\n"); + return 0; + } + if (strlen(timeout) && (sscanf(priority, "%d", &to) != 1)) { + send_error(s, "Invalid timeout\n"); + return 0; + } + strncpy(tmp, name, sizeof(tmp) - 1); + tech = tmp; + data = strchr(tmp, '/'); + if (!data) { + send_error(s, "Invalid channel\n"); + return 0; + } + *data = '\0'; + data++; + res = ast_pbx_outgoing_exten(tech, AST_FORMAT_SLINEAR, data, to, context, exten, pi, &reason, 0); + if (!res) + send_ack(s, "Originate successfully queued"); + else + send_error(s, "Originate failed"); + return 0; +} + +static struct action { + char *action; + int authority; + int (*func)(struct mansession *s, struct message *m); +} actions[] = { + { "Ping", 0, action_ping }, + { "Logoff", 0, action_logoff }, + { "Hangup", EVENT_FLAG_CALL, action_hangup }, + { "Status", EVENT_FLAG_CALL, action_status }, + { "Redirect", EVENT_FLAG_CALL, action_redirect }, + { "Originate", EVENT_FLAG_CALL, action_originate }, +}; + +static int process_message(struct mansession *s, struct message *m) +{ + int x; + char action[80]; + strncpy(action, get_header(m, "Action"), sizeof(action)); + if (!strlen(action)) { + send_error(s, "Missing action in request"); + return 0; + } + if (!s->authenticated) { + if (!strcasecmp(action, "Login")) { + if (authenticate(s, m)) { + sleep(1); + send_error(s, "Authentication failed"); + return -1; + } else { + s->authenticated = 1; + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Manager '%s' logged on from %s\n", s->username, inet_ntoa(s->sin.sin_addr)); + ast_log(LOG_EVENT, "Manager '%s' logged off from %s\n", s->username, inet_ntoa(s->sin.sin_addr)); + send_ack(s, "Authentication accepted"); + } + } else + send_error(s, "Authentication Required"); + } else { + for (x=0;x<sizeof(actions) / sizeof(actions[0]);x++) { + if (!strcasecmp(action, actions[x].action)) { + if ((s->writeperm & actions[x].authority) == actions[x].authority) { + if (actions[x].func(s, m)) + return -1; + } else { + send_error(s, "Permission denied"); + } + break; + } + } + if (x >= sizeof(actions) / sizeof(actions[0])) + send_error(s, "Invalid/unknown command"); + } +#if 0 + for (x=0;x<m->hdrcount;x++) { + printf("Header: %s\n", m->headers[x]); + } +#endif + return 0; +} + +static int get_input(struct mansession *s, char *output) +{ + /* output must have at least sizeof(s->inbuf) space */ + int res; + int x; + fd_set fds; + for (x=1;x<s->inlen;x++) { + if ((s->inbuf[x] == '\n') && (s->inbuf[x-1] == '\r')) { + /* Copy output data up to and including \r\n */ + memcpy(output, s->inbuf, x + 1); + /* Add trailing \0 */ + output[x+1] = '\0'; + /* Move remaining data back to the front */ + memmove(s->inbuf, s->inbuf + x + 1, s->inlen - x); + s->inlen -= (x + 1); + return 1; + } + } + if (s->inlen >= sizeof(s->inbuf) - 1) { + ast_log(LOG_WARNING, "Dumping long line with no return from %s: %s\n", inet_ntoa(s->sin.sin_addr), s->inbuf); + s->inlen = 0; + } + FD_ZERO(&fds); + FD_SET(s->fd, &fds); + res = select(s->fd + 1, &fds, NULL, NULL, NULL); + if (res < 0) { + ast_log(LOG_WARNING, "Select returned error: %s\n", strerror(errno)); + } else if (res > 0) { + ast_pthread_mutex_lock(&s->lock); + res = read(s->fd, s->inbuf + s->inlen, sizeof(s->inbuf) - 1 - s->inlen); + ast_pthread_mutex_unlock(&s->lock); + if (res < 1) + return -1; + } + s->inlen += res; + s->inbuf[s->inlen] = '\0'; + return 0; +} + +static void *session_do(void *data) +{ + struct mansession *s = data; + struct message m; + int res; + + ast_pthread_mutex_lock(&s->lock); + ast_cli(s->fd, "Asterisk Call Manager/1.0\r\n"); + ast_pthread_mutex_unlock(&s->lock); + memset(&m, 0, sizeof(&m)); + for (;;) { + res = get_input(s, m.headers[m.hdrcount]); + if (res > 0) { + /* Strip trailing \r\n */ + if (strlen(m.headers[m.hdrcount]) < 2) + continue; + m.headers[m.hdrcount][strlen(m.headers[m.hdrcount]) - 2] = '\0'; + if (!strlen(m.headers[m.hdrcount])) { + if (process_message(s, &m)) + break; + memset(&m, 0, sizeof(&m)); + } else if (m.hdrcount < MAX_HEADERS - 1) + m.hdrcount++; + } else if (res < 0) + break; + } + if (s->authenticated) { + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Manager '%s' logged off from %s\n", s->username, inet_ntoa(s->sin.sin_addr)); + ast_log(LOG_EVENT, "Manager '%s' logged off from %s\n", s->username, inet_ntoa(s->sin.sin_addr)); + } else { + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Connect attempt from '%s' unable to authenticate\n", inet_ntoa(s->sin.sin_addr)); + ast_log(LOG_EVENT, "Failed attempt from %s\n", inet_ntoa(s->sin.sin_addr)); + } + destroy_session(s); + return NULL; +} + +static void *accept_thread(void *ignore) +{ + int as; + struct sockaddr_in sin; + int sinlen; + struct mansession *s; + for (;;) { + sinlen = sizeof(sin); + as = accept(asock, &sin, &sinlen); + if (as < 0) { + ast_log(LOG_NOTICE, "Accept returned -1: %s\n", strerror(errno)); + continue; + } + s = malloc(sizeof(struct mansession)); + if (!s) { + ast_log(LOG_WARNING, "Failed to allocate management session: %s\n", strerror(errno)); + continue; + } + memset(s, 0, sizeof(struct mansession)); + memcpy(&s->sin, &sin, sizeof(sin)); + ast_pthread_mutex_init(&s->lock); + s->fd = as; + ast_pthread_mutex_lock(&sessionlock); + s->next = sessions; + sessions = s; + ast_pthread_mutex_unlock(&sessionlock); + if (pthread_create(&t, NULL, session_do, s)) + destroy_session(s); + } + +} + +int manager_event(int category, char *event, char *fmt, ...) +{ + struct mansession *s; + char tmp[4096]; + va_list ap; + + ast_pthread_mutex_lock(&sessionlock); + s = sessions; + while(s) { + if ((s->readperm & category) == category) { + ast_pthread_mutex_lock(&s->lock); + ast_cli(s->fd, "Event: %s\r\n", event); + va_start(ap, fmt); + vsnprintf(tmp, sizeof(tmp), fmt, ap); + va_end(ap); + write(s->fd, tmp, strlen(tmp)); + ast_cli(s->fd, "\r\n"); + ast_pthread_mutex_unlock(&s->lock); + } + s = s->next; + } + ast_pthread_mutex_unlock(&sessionlock); + return 0; +} + +int init_manager(void) +{ + struct ast_config *cfg; + char *val; + int oldportno = portno; + static struct sockaddr_in ba; + int x = 1; + + portno = DEFAULT_MANAGER_PORT; + cfg = ast_load("manager.conf"); + if (!cfg) { + ast_log(LOG_NOTICE, "Unable to open management configuration manager.conf. Call management disabled.\n"); + return 0; + } + memset(&ba, 0, sizeof(ba)); + val = ast_variable_retrieve(cfg, "general", "enabled"); + if (val) + enabled = ast_true(val); + + if ((val = ast_variable_retrieve(cfg, "general", "portno"))) { + if (sscanf(val, "%d", &portno) != 1) { + ast_log(LOG_WARNING, "Invalid port number '%s'\n", val); + portno = DEFAULT_MANAGER_PORT; + } + } + + ba.sin_family = AF_INET; + ba.sin_port = htons(portno); + memset(&ba.sin_addr, 0, sizeof(ba.sin_addr)); + + if ((val = ast_variable_retrieve(cfg, "general", "bindaddr"))) { + if (!inet_aton(val, &ba.sin_addr)) { + ast_log(LOG_WARNING, "Invalid address '%s' specified, using 0.0.0.0\n", val); + memset(&ba.sin_addr, 0, sizeof(ba.sin_addr)); + } + } + + if ((asock > -1) && ((portno != oldportno) || !enabled)) { +#if 0 + /* Can't be done yet */ + close(asock); + asock = -1; +#else + ast_log(LOG_WARNING, "Unable to change management port / enabled\n"); +#endif + } + /* If not enabled, do nothing */ + if (!enabled) + return 0; + if (asock < 0) { + asock = socket(AF_INET, SOCK_STREAM, 0); + if (asock < 0) { + ast_log(LOG_WARNING, "Unable to create socket: %s\n", strerror(errno)); + return -1; + } + setsockopt(asock, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x)); + if (bind(asock, &ba, sizeof(ba))) { + ast_log(LOG_WARNING, "Unable to bind socket: %s\n", strerror(errno)); + close(asock); + asock = -1; + return -1; + } + if (listen(asock, 2)) { + ast_log(LOG_WARNING, "Unable to listen on socket: %s\n", strerror(errno)); + close(asock); + asock = -1; + return -1; + } + if (option_verbose) + ast_verbose("Asterisk Management interface listening on port %d\n", portno); + pthread_create(&t, NULL, accept_thread, NULL); + } + ast_destroy(cfg); + return 0; +} + +int reload_manager(void) +{ + return init_manager(); +} |