aboutsummaryrefslogtreecommitdiffstats
path: root/manager.c
diff options
context:
space:
mode:
authormarkster <markster@f38db490-d61c-443f-a65b-d21fe96a405b>2002-09-12 18:32:42 +0000
committermarkster <markster@f38db490-d61c-443f-a65b-d21fe96a405b>2002-09-12 18:32:42 +0000
commit39ec885c0e48096095a8a3beecb2b59c5ed76fc7 (patch)
tree13e613480960be9a46402cd1706f63c7a3ab3f7f /manager.c
parent4adedfc0b7d2e76a4b96438dc5ce2d8659ac134d (diff)
Version 0.2.0 from FTP
git-svn-id: http://svn.digium.com/svn/asterisk/trunk@526 f38db490-d61c-443f-a65b-d21fe96a405b
Diffstat (limited to 'manager.c')
-rwxr-xr-xmanager.c619
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();
+}