[asterisk-commits] trunk r16850 - in /trunk: ./ configs/ doc/
include/asterisk/ static-http/
asterisk-commits at lists.digium.com
asterisk-commits at lists.digium.com
Sat Apr 1 01:49:56 MST 2006
Author: markster
Date: Sat Apr 1 02:49:54 2006
New Revision: 16850
URL: http://svn.digium.com/view/asterisk?rev=16850&view=rev
Log:
Flesh out the remainder of the manager + http changes and create a sample application to partially
demonstrate the capability of manager over http.
Added:
trunk/doc/ajam.txt (with props)
trunk/static-http/
trunk/static-http/ajamdemo.html (with props)
trunk/static-http/astman.css (with props)
trunk/static-http/astman.js (with props)
trunk/static-http/prototype.js (with props)
Modified:
trunk/Makefile
trunk/configs/http.conf.sample
trunk/configs/manager.conf.sample
trunk/http.c
trunk/include/asterisk/http.h
trunk/manager.c
Modified: trunk/Makefile
URL: http://svn.digium.com/view/asterisk/trunk/Makefile?rev=16850&r1=16849&r2=16850&view=diff
==============================================================================
--- trunk/Makefile (original)
+++ trunk/Makefile Sat Apr 1 02:49:54 2006
@@ -566,6 +566,13 @@
datafiles: all
if [ x`$(ID) -un` = xroot ]; then sh build_tools/mkpkgconfig $(DESTDIR)/usr/lib/pkgconfig; fi
+ # Should static HTTP be installed during make samples or even with its own target ala
+ # webvoicemail? There are portions here that *could* be customized but might also be
+ # improved a lot. I'll put it here for now.
+ mkdir -p $(DESTDIR)$(ASTVARLIBDIR)/static-http
+ for x in static-http/*; do \
+ install -m 644 $$x $(DESTDIR)$(ASTVARLIBDIR)/static-http ; \
+ done
mkdir -p $(DESTDIR)$(ASTVARLIBDIR)/sounds/digits
mkdir -p $(DESTDIR)$(ASTVARLIBDIR)/sounds/priv-callerintros
for x in sounds/digits/*.gsm; do \
Modified: trunk/configs/http.conf.sample
URL: http://svn.digium.com/view/asterisk/trunk/configs/http.conf.sample?rev=16850&r1=16849&r2=16850&view=diff
==============================================================================
--- trunk/configs/http.conf.sample (original)
+++ trunk/configs/http.conf.sample Sat Apr 1 02:49:54 2006
@@ -4,15 +4,20 @@
;
[general]
;
-; Whether HTTP interface is enabled or not.
+; Whether HTTP interface is enabled or not. Default is no.
;
-enabled=no
+;enabled=yes
;
-; Address to bind to
+; Whether Asterisk should serve static content from http-static
+; Default is no.
+;
+;enablestatic=yes
+;
+; Address to bind to. Default is 0.0.0.0
;
bindaddr=127.0.0.1
;
-; Port to bind to
+; Port to bind to (default is 8088)
;
bindport=8088
;
Modified: trunk/configs/manager.conf.sample
URL: http://svn.digium.com/view/asterisk/trunk/configs/manager.conf.sample?rev=16850&r1=16849&r2=16850&view=diff
==============================================================================
--- trunk/configs/manager.conf.sample (original)
+++ trunk/configs/manager.conf.sample Sat Apr 1 02:49:54 2006
@@ -13,11 +13,18 @@
; ---------------------------- SECURITY NOTE -------------------------------
; Note that you should not enable the AMI on a public IP address. If needed,
; block this TCP port with iptables (or another FW software) and reach it
-; with IPsec, SSH, or SSL vpn tunnel
+; with IPsec, SSH, or SSL vpn tunnel. You can also make the manager
+; interface available over http if Asterisk's http server is enabled in
+; http.conf and if both "enabled" and "webenabled" are set to yes in
+; this file. Both default to no. httptimeout provides the maximum
+; timeout in seconds before a web based session is discarded. The
+; default is 60 seconds.
;
[general]
enabled = no
+;webenabled = yes
port = 5038
+;httptimeout = 60
bindaddr = 0.0.0.0
;displayconnects = yes
;
Added: trunk/doc/ajam.txt
URL: http://svn.digium.com/view/asterisk/trunk/doc/ajam.txt?rev=16850&view=auto
==============================================================================
--- trunk/doc/ajam.txt (added)
+++ trunk/doc/ajam.txt Sat Apr 1 02:49:54 2006
@@ -1,0 +1,91 @@
+Asynchronous Javascript Asterisk Manger (AJAM)
+==============================================
+
+AJAM is a new technology which allows web browsers or other HTTP enabled
+applications and web pages to directly access the Asterisk Manger
+Interface (AMI) via HTTP. Setting up your server to process AJAM
+involves a few steps:
+
+Setup the Asterisk HTTP server
+------------------------------
+
+1) Uncomment the line "enabled=yes" in /etc/asterisk/http.conf to enable
+ Asterisk's builtin micro HTTP server.
+
+2) If you want Asterisk to actually deliver simple HTML pages, CSS,
+ javascript, etc. you should uncomment "enablestatic=yes"
+
+3) Adjust your "bindaddr" and "bindport" settings as appropriate for
+ your desired accessibility
+
+4) Adjust your "prefix" if appropriate, which must be the beginning of
+ any URI on the server to match. The default is "asterisk" and the
+ rest of these instructions assume that value.
+
+Allow Manager Access via HTTP
+-----------------------------
+
+1) Make sure you have both "enabled = yes" and "webenabled = yes" setup
+ in /etc/asterisk/manager.conf
+
+2) You may also use "httptimeout" to set a default timeout for HTTP
+ connections.
+
+3) Make sure you have a manager username/secret
+
+Once those configurations are complete you can reload or restart
+Asterisk and you should be able to point your web browser to specific
+URI's which will allow you to access various web functions. A complete
+list can be found by typing "show http" at the Asterisk CLI.
+
+examples:
+
+http://localhost:8088/asterisk/manager?action=login&username=foo&secret=bar
+
+This logs you into the manager interface's "HTML" view. Once you're
+logged in, Asterisk stores a cookie on your browser (valid for the
+length of httptimeout) which is used to connect to the same session.
+
+http://localhost:8088/asterisk/rawman?action=status
+
+Assuming you've already logged into manager, this URI will give you a
+"raw" manager output for the "status" command.
+
+http://localhost:8088/asterisk/mxml?action=status
+
+This will give you the same status view but represented as AJAX data,
+theoretically compatible with RICO (http://www.openrico.org).
+
+http://localhost:8088/asterisk/static/ajamdemo.html
+
+If you have enabled static content support and have done a make install,
+Asterisk will serve up a demo page which presents a live, but very
+basic, "astman" like interface. You can login with your username/secret
+for manager and have a basic view of channels as well as transfer and
+hangup calls. It's only tested in Firefox, but could probably be made
+to run in other browsers as well.
+
+A sample library (astman.js) is included to help ease the creation of
+manager HTML interfaces.
+
+Note that for the demo, there is no need for *any* external web server.
+
+Integration with other web servers
+----------------------------------
+
+Asterisk's micro HTTP server is *not* designed to replace a general
+purpose web server and it is intentionally created to provide only the
+minimal interfaces required. Even without the addition of an external
+web server, one can use Asterisk's interfaces to implement screen pops
+and similar tools pulling data from other web servers using iframes,
+div's etc. If you want to integrate CGI's, databases, PHP, etc. you
+will likely need to use a more traditional web server like Apache and
+link in your Asterisk micro HTTP server with something like this:
+
+ProxyPass /asterisk http://localhost:8088/asterisk
+
+This is a fairly new technology so I'd love to hear if it's useful for
+you!
+
+Mark
+
Propchange: trunk/doc/ajam.txt
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: trunk/doc/ajam.txt
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision
Propchange: trunk/doc/ajam.txt
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified: trunk/http.c
URL: http://svn.digium.com/view/asterisk/trunk/http.c?rev=16850&r1=16849&r2=16850&view=diff
==============================================================================
--- trunk/http.c (original)
+++ trunk/http.c Sat Apr 1 02:49:54 2006
@@ -33,16 +33,20 @@
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/socket.h>
+#include <sys/stat.h>
#include <sys/signal.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
+#include "asterisk.h"
#include "asterisk/cli.h"
#include "asterisk/http.h"
#include "asterisk/utils.h"
#include "asterisk/strings.h"
+#include "asterisk/options.h"
+#include "asterisk/config.h"
#define MAX_PREFIX 80
#define DEFAULT_PREFIX "asterisk"
@@ -61,6 +65,100 @@
static char prefix[MAX_PREFIX];
static int prefix_len = 0;
static struct sockaddr_in oldsin;
+static int enablestatic=0;
+
+/* Limit the kinds of files we're willing to serve up */
+static struct {
+ char *ext;
+ char *mtype;
+} mimetypes[] = {
+ { "png", "image/png" },
+ { "jpg", "image/jpeg" },
+ { "js", "application/x-javascript" },
+ { "wav", "audio/x-wav" },
+ { "mp3", "audio/mpeg" },
+};
+
+static char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
+{
+ int x;
+ if (ftype) {
+ for (x=0;x<sizeof(mimetypes) / sizeof(mimetypes[0]); x++) {
+ if (!strcasecmp(ftype, mimetypes[x].ext))
+ return mimetypes[x].mtype;
+ }
+ }
+ snprintf(wkspace, wkspacelen, "text/%s", ftype ? ftype : "plain");
+ return wkspace;
+}
+
+static char *static_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
+{
+ char result[4096];
+ char *c=result;
+ char *path;
+ char *ftype, *mtype;
+ char wkspace[80];
+ struct stat st;
+ int len;
+ int fd;
+ void *blob;
+
+ /* Yuck. I'm not really sold on this, but if you don't deliver static content it makes your configuration
+ substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
+ if (!enablestatic || ast_strlen_zero(uri))
+ goto out403;
+ /* Disallow any funny filenames at all */
+ if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0]))
+ goto out403;
+ if (strstr(uri, "/.."))
+ goto out403;
+
+ if ((ftype = strrchr(uri, '.')))
+ ftype++;
+ mtype=ftype2mtype(ftype, wkspace, sizeof(wkspace));
+
+ /* Cap maximum length */
+ len = strlen(uri) + strlen(ast_config_AST_VAR_DIR) + strlen("/static-http/") + 5;
+ if (len > 1024)
+ goto out403;
+
+ path = alloca(len);
+ sprintf(path, "%s/static-http/%s", ast_config_AST_VAR_DIR, uri);
+ if (stat(path, &st))
+ goto out404;
+ if (S_ISDIR(st.st_mode))
+ goto out404;
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ goto out403;
+
+ len = st.st_size + strlen(mtype) + 40;
+
+ blob = malloc(len);
+ if (blob) {
+ c = blob;
+ sprintf(c, "Content-type: %s\r\n\r\n", mtype);
+ c += strlen(c);
+ *contentlength = read(fd, c, st.st_size);
+ if (*contentlength < 0) {
+ close(fd);
+ free(blob);
+ goto out403;
+ }
+ }
+ return blob;
+
+out404:
+ *status = 404;
+ *title = strdup("Not Found");
+ return ast_http_error(404, "Not Found", NULL, "Nothing to see here. Move along.");
+
+out403:
+ *status = 403;
+ *title = strdup("Access Denied");
+ return ast_http_error(403, "Access Denied", NULL, "Sorry, I cannot let you do that, Dave.");
+}
static char *httpstatus_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
@@ -86,7 +184,15 @@
ast_build_string(&c, &reslen, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
v = vars;
while(v) {
- ast_build_string(&c, &reslen, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
+ if (strncasecmp(v->name, "cookie_", 7))
+ ast_build_string(&c, &reslen, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
+ v = v->next;
+ }
+ ast_build_string(&c, &reslen, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
+ v = vars;
+ while(v) {
+ if (!strncasecmp(v->name, "cookie_", 7))
+ ast_build_string(&c, &reslen, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
v = v->next;
}
ast_build_string(&c, &reslen, "</table><center><font size=\"-1\"><i>Asterisk and Digium are registered trademarks of Digium, Inc.</i></font></center></body>\r\n");
@@ -98,6 +204,13 @@
.description = "Asterisk HTTP General Status",
.uri = "httpstatus",
.has_subtree = 0,
+};
+
+static struct ast_http_uri staticuri = {
+ .callback = static_callback,
+ .description = "Asterisk HTTP Static Delivery",
+ .uri = "static",
+ .has_subtree = 1,
};
char *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
@@ -153,7 +266,7 @@
}
}
-static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **title, int *contentlength)
+static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **title, int *contentlength, struct ast_variable **cookies)
{
char *c;
char *turi;
@@ -176,9 +289,9 @@
if (val) {
*val = '\0';
val++;
+ ast_uri_decode(val);
} else
val = "";
- ast_uri_decode(val);
ast_uri_decode(var);
if ((v = ast_variable_new(var, val))) {
if (vars)
@@ -189,6 +302,11 @@
}
}
}
+ if (prev)
+ prev->next = *cookies;
+ else
+ vars = *cookies;
+ *cookies = NULL;
ast_uri_decode(uri);
if (!strncasecmp(uri, prefix, prefix_len)) {
uri += prefix_len;
@@ -227,9 +345,12 @@
static void *ast_httpd_helper_thread(void *data)
{
char buf[4096];
+ char cookie[4096];
char timebuf[256];
struct ast_http_server_instance *ser = data;
+ struct ast_variable *var, *prev=NULL, *vars=NULL;
char *uri, *c, *title=NULL;
+ char *vname, *vval;
int status = 200, contentlength = 0;
time_t t;
@@ -252,25 +373,68 @@
*c = '\0';
}
}
+
+ while (fgets(cookie, sizeof(cookie), ser->f)) {
+ /* Trim trailing characters */
+ while(!ast_strlen_zero(cookie) && (cookie[strlen(cookie) - 1] < 33)) {
+ cookie[strlen(cookie) - 1] = '\0';
+ }
+ if (ast_strlen_zero(cookie))
+ break;
+ if (!strncasecmp(cookie, "Cookie: ", 8)) {
+ vname = cookie + 8;
+ vval = strchr(vname, '=');
+ if (vval) {
+ /* Ditch the = and the quotes */
+ *vval = '\0';
+ vval++;
+ if (*vval)
+ vval++;
+ if (strlen(vval))
+ vval[strlen(vval) - 1] = '\0';
+ var = ast_variable_new(vname, vval);
+ if (var) {
+ if (prev)
+ prev->next = var;
+ else
+ vars = var;
+ prev = var;
+ }
+ }
+ }
+ }
+
if (*uri) {
if (!strcasecmp(buf, "get"))
- c = handle_uri(&ser->requestor, uri, &status, &title, &contentlength);
+ c = handle_uri(&ser->requestor, uri, &status, &title, &contentlength, &vars);
else
c = ast_http_error(501, "Not Implemented", NULL, "Attempt to use unimplemented / unsupported method");\
} else
c = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
+
+ /* If they aren't mopped up already, clean up the cookies */
+ if (vars)
+ ast_variables_destroy(vars);
+
if (!c)
c = ast_http_error(500, "Internal Error", NULL, "Internal Server Error");
if (c) {
time(&t);
strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
- ast_cli(ser->fd, "HTTP/1.1 GET %d %s\r\n", status, title ? title : "OK");
+ ast_cli(ser->fd, "HTTP/1.1 %d %s\r\n", status, title ? title : "OK");
ast_cli(ser->fd, "Server: Asterisk\r\n");
ast_cli(ser->fd, "Date: %s\r\n", timebuf);
- if (contentlength)
- ast_cli(ser->fd, "Content-length: %d\r\n", contentlength);
ast_cli(ser->fd, "Connection: close\r\n");
- ast_cli(ser->fd, "%s", c);
+ if (contentlength) {
+ char *tmp;
+ tmp = strstr(c, "\r\n\r\n");
+ if (tmp) {
+ ast_cli(ser->fd, "Content-length: %d\r\n", contentlength);
+ write(ser->fd, c, (tmp + 4 - c));
+ write(ser->fd, tmp + 4, contentlength);
+ }
+ } else
+ ast_cli(ser->fd, "%s", c);
free(c);
}
if (title)
@@ -297,25 +461,40 @@
ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
continue;
}
- if (!(ser = ast_calloc(1, sizeof(*ser)))) {
- close(fd);
- continue;
- }
- ser->fd = fd;
- if ((ser->f = fdopen(ser->fd, "w+"))) {
- if (ast_pthread_create(&launched, NULL, ast_httpd_helper_thread, ser)) {
- ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
- fclose(ser->f);
+ ser = ast_calloc(1, sizeof(*ser));
+ if (ser) {
+ ser->fd = fd;
+ memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
+ if ((ser->f = fdopen(ser->fd, "w+"))) {
+ if (ast_pthread_create(&launched, NULL, ast_httpd_helper_thread, ser)) {
+ ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
+ fclose(ser->f);
+ free(ser);
+ }
+ } else {
+ ast_log(LOG_WARNING, "fdopen failed!\n");
+ close(ser->fd);
free(ser);
}
} else {
- ast_log(LOG_WARNING, "fdopen failed!\n");
close(ser->fd);
free(ser);
}
}
return NULL;
}
+
+char *ast_http_setcookie(const char *var, const char *val, int expires, char *buf, int buflen)
+{
+ char *c;
+ c = buf;
+ ast_build_string(&c, &buflen, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var, val);
+ if (expires)
+ ast_build_string(&c, &buflen, "; Max-Age=%d", expires);
+ ast_build_string(&c, &buflen, "\r\n");
+ return buf;
+}
+
static void http_server_start(struct sockaddr_in *sin)
{
@@ -383,6 +562,7 @@
struct ast_config *cfg;
struct ast_variable *v;
int enabled=0;
+ int newenablestatic=0;
struct sockaddr_in sin;
struct hostent *hp;
struct ast_hostent ahp;
@@ -396,6 +576,8 @@
while(v) {
if (!strcasecmp(v->name, "enabled"))
enabled = ast_true(v->value);
+ else if (!strcasecmp(v->name, "enablestatic"))
+ newenablestatic = ast_true(v->value);
else if (!strcasecmp(v->name, "bindport"))
sin.sin_port = ntohs(atoi(v->value));
else if (!strcasecmp(v->name, "bindaddr")) {
@@ -416,6 +598,7 @@
ast_copy_string(prefix, newprefix, sizeof(prefix));
prefix_len = strlen(prefix);
}
+ enablestatic = newenablestatic;
http_server_start(&sin);
return 0;
}
@@ -462,6 +645,7 @@
int ast_http_init(void)
{
ast_http_uri_link(&statusuri);
+ ast_http_uri_link(&staticuri);
ast_cli_register_multiple(http_cli, sizeof(http_cli) / sizeof(http_cli[0]));
return __ast_http_load(0);
}
Modified: trunk/include/asterisk/http.h
URL: http://svn.digium.com/view/asterisk/trunk/include/asterisk/http.h?rev=16850&r1=16849&r2=16850&view=diff
==============================================================================
--- trunk/include/asterisk/http.h (original)
+++ trunk/include/asterisk/http.h Sat Apr 1 02:49:54 2006
@@ -58,6 +58,8 @@
/* Destroy an HTTP server */
void ast_http_uri_unlink(struct ast_http_uri *urihandler);
+char *ast_http_setcookie(const char *var, const char *val, int expires, char *buf, int buflen);
+
int ast_http_init(void);
int ast_http_reload(void);
Modified: trunk/manager.c
URL: http://svn.digium.com/view/asterisk/trunk/manager.c?rev=16850&r1=16849&r2=16850&view=diff
==============================================================================
--- trunk/manager.c (original)
+++ trunk/manager.c Sat Apr 1 02:49:54 2006
@@ -35,6 +35,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <ctype.h>
#include <sys/time.h>
#include <sys/types.h>
#include <netdb.h>
@@ -64,6 +65,7 @@
#include "asterisk/md5.h"
#include "asterisk/acl.h"
#include "asterisk/utils.h"
+#include "asterisk/http.h"
struct fast_originate_helper {
char tech[AST_MAX_MANHEADER_LEN];
@@ -86,6 +88,7 @@
static int asock = -1;
static int displayconnects = 1;
static int timestampevents = 0;
+static int httptimeout = 60;
static pthread_t t;
AST_MUTEX_DEFINE_STATIC(sessionlock);
@@ -119,6 +122,18 @@
int busy;
/*! Whether or not we're "dead" */
int dead;
+ /*! Whether an HTTP manager is in use */
+ int inuse;
+ /*! Whether an HTTP session should be destroyed */
+ int needdestroy;
+ /*! Whether an HTTP session has someone waiting on events */
+ pthread_t waiting_thread;
+ /*! Unique manager identifer */
+ unsigned long managerid;
+ /*! Session timeout if HTTP */
+ time_t sessiontimeout;
+ /*! Output from manager interface */
+ char *outputstr;
/*! Logged in username */
char username[80];
/*! Authentication challenge */
@@ -212,11 +227,168 @@
return ret;
}
+static void xml_copy_escape(char **dst, int *maxlen, const char *src, int lower)
+{
+ while (*src && (*maxlen > 6)) {
+ switch(*src) {
+ case '<':
+ strcpy(*dst, "<");
+ (*dst) += 4;
+ *maxlen -= 4;
+ break;
+ case '>':
+ strcpy(*dst, ">");
+ (*dst) += 4;
+ *maxlen -= 4;
+ break;
+ case '\"':
+ strcpy(*dst, """);
+ (*dst) += 6;
+ *maxlen -= 6;
+ break;
+ case '\'':
+ strcpy(*dst, "'");
+ (*dst) += 6;
+ *maxlen -= 6;
+ break;
+ case '&':
+ strcpy(*dst, "&");
+ (*dst) += 4;
+ *maxlen -= 4;
+ break;
+ default:
+ *(*dst)++ = lower ? tolower(*src) : *src;
+ (*maxlen)--;
+ }
+ src++;
+ }
+}
+static char *xml_translate(char *in, struct ast_variable *vars)
+{
+ struct ast_variable *v;
+ char *dest=NULL;
+ char *out, *tmp, *var, *val;
+ char *objtype=NULL;
+ int colons = 0;
+ int breaks = 0;
+ int len;
+ int count = 1;
+ int escaped = 0;
+ int inobj = 0;
+ int x;
+ v = vars;
+ while(v) {
+ if (!dest && !strcasecmp(v->name, "ajaxdest"))
+ dest = v->value;
+ else if (!objtype && !strcasecmp(v->name, "ajaxobjtype"))
+ objtype = v->value;
+ v = v->next;
+ }
+ if (!dest)
+ dest = "unknown";
+ if (!objtype)
+ objtype = "generic";
+ for (x=0;in[x];x++) {
+ if (in[x] == ':')
+ colons++;
+ else if (in[x] == '\n')
+ breaks++;
+ else if (strchr("&\"<>", in[x]))
+ escaped++;
+ }
+ len = strlen(in) + colons * 5 + breaks * (40 + strlen(dest) + strlen(objtype)) + escaped * 10; /* foo="bar", "<response type=\"object\" id=\"dest\"", "&" */
+ out = malloc(len);
+ if (!out)
+ return 0;
+ tmp = out;
+ while(*in) {
+ var = in;
+ while (*in && (*in >= 32)) in++;
+ if (*in) {
+ if ((count > 3) && inobj) {
+ ast_build_string(&tmp, &len, " /></response>\n");
+ inobj = 0;
+ }
+ count = 0;
+ while (*in && (*in < 32)) {
+ *in = '\0';
+ in++;
+ count++;
+ }
+ val = strchr(var, ':');
+ if (val) {
+ *val = '\0';
+ val++;
+ if (*val == ' ')
+ val++;
+ if (!inobj) {
+ ast_build_string(&tmp, &len, "<response type='object' id='%s'><%s", dest, objtype);
+ inobj = 1;
+ }
+ ast_build_string(&tmp, &len, " ");
+ xml_copy_escape(&tmp, &len, var, 1);
+ ast_build_string(&tmp, &len, "='");
+ xml_copy_escape(&tmp, &len, val, 0);
+ ast_build_string(&tmp, &len, "'");
+ }
+ }
+ }
+ if (inobj)
+ ast_build_string(&tmp, &len, " /></response>\n");
+ return out;
+}
+
+static char *html_translate(char *in)
+{
+ int x;
+ int colons = 0;
+ int breaks = 0;
+ int len;
+ int count=1;
+ char *tmp, *var, *val, *out;
+ for (x=0;in[x];x++) {
+ if (in[x] == ':')
+ colons++;
+ if (in[x] == '\n')
+ breaks++;
+ }
+ len = strlen(in) + colons * 40 + breaks * 40; /* <tr><td></td><td></td></tr>, "<tr><td colspan=\"2\"><hr></td></tr> */
+ out = malloc(len);
+ if (!out)
+ return 0;
+ tmp = out;
+ while(*in) {
+ var = in;
+ while (*in && (*in >= 32)) in++;
+ if (*in) {
+ if ((count % 4) == 0){
+ ast_build_string(&tmp, &len, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
+ }
+ count = 0;
+ while (*in && (*in < 32)) {
+ *in = '\0';
+ in++;
+ count++;
+ }
+ val = strchr(var, ':');
+ if (val) {
+ *val = '\0';
+ val++;
+ if (*val == ' ')
+ val++;
+ ast_build_string(&tmp, &len, "<tr><td>%s</td><td>%s</td></tr>\r\n", var, val);
+ }
+ }
+ }
+ return out;
+}
+
void astman_append(struct mansession *s, const char *fmt, ...)
{
char *stuff;
int res;
va_list ap;
+ char *tmp;
va_start(ap, fmt);
res = vasprintf(&stuff, fmt, ap);
@@ -224,7 +396,17 @@
if (res == -1) {
ast_log(LOG_ERROR, "Memory allocation failure\n");
} else {
- ast_carefulwrite(s->fd, stuff, strlen(stuff), 100);
+ if (s->fd > -1)
+ ast_carefulwrite(s->fd, stuff, strlen(stuff), 100);
+ else {
+ tmp = realloc(s->outputstr, (s->outputstr ? strlen(s->outputstr) : 0) + strlen(stuff) + 1);
+ if (tmp) {
+ if (!s->outputstr)
+ tmp[0] = '\0';
+ s->outputstr = tmp;
+ strcat(s->outputstr, stuff);
+ }
+ }
free(stuff);
}
}
@@ -320,6 +502,8 @@
struct eventqent *eqe;
if (s->fd > -1)
close(s->fd);
+ if (s->outputstr)
+ free(s->outputstr);
ast_mutex_destroy(&s->__lock);
while(s->eventq) {
eqe = s->eventq;
@@ -606,7 +790,7 @@
return -1;
}
}
- } else if (password && !strcasecmp(password, pass)) {
+ } else if (password && !strcmp(password, pass)) {
break;
} else {
ast_log(LOG_NOTICE, "%s failed to authenticate as '%s'\n", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr), user);
@@ -633,13 +817,101 @@
/*! \brief PING: Manager PING */
static char mandescr_ping[] =
-"Description: A 'Ping' action will ellicit a 'Pong' response. Used to keep the "
+"Description: A 'Ping' action will ellicit a 'Pong' response. Used to keep the\n"
" manager connection open.\n"
"Variables: NONE\n";
static int action_ping(struct mansession *s, struct message *m)
{
astman_send_response(s, m, "Pong", NULL);
+ return 0;
+}
+
+/*! \brief WAITEVENT: Manager WAITEVENT */
+static char mandescr_waitevent[] =
+"Description: A 'WaitEvent' action will ellicit a 'Success' response. Whenever\n"
+"a manager event is queued. Once WaitEvent has been called on an HTTP manager\n"
+"session, events will be generated and queued.\n"
+"Variables: \n"
+" Timeout: Maximum time to wait for events\n";
+
+static int action_waitevent(struct mansession *s, struct message *m)
+{
+ char *timeouts = astman_get_header(m, "Timeout");
+ int timeout = -1, max;
+ int x;
+ int needexit = 0;
+ time_t now;
+ struct eventqent *eqe;
+ char *id = astman_get_header(m,"ActionID");
+ char idText[256]="";
+
+ if (!ast_strlen_zero(id))
+ snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
+
+ if (!ast_strlen_zero(timeouts)) {
+ sscanf(timeouts, "%i", &timeout);
+ }
+
+ ast_mutex_lock(&s->__lock);
+ if (s->waiting_thread != AST_PTHREADT_NULL) {
+ pthread_kill(s->waiting_thread, SIGURG);
+ }
+ if (s->sessiontimeout) {
+ time(&now);
+ max = s->sessiontimeout - now - 10;
+ if (max < 0)
+ max = 0;
+ if ((timeout < 0) || (timeout > max))
+ timeout = max;
+ if (!s->send_events)
+ s->send_events = -1;
+ /* Once waitevent is called, always queue events from now on */
+ if (s->busy == 1)
+ s->busy = 2;
+ }
+ ast_mutex_unlock(&s->__lock);
+ s->waiting_thread = pthread_self();
+
+ ast_log(LOG_DEBUG, "Starting waiting for an event!\n");
+ for (x=0;((x<timeout) || (timeout < 0)); x++) {
+ ast_mutex_lock(&s->__lock);
+ if (s->eventq)
+ needexit = 1;
+ if (s->waiting_thread != pthread_self())
+ needexit = 1;
+ if (s->needdestroy)
+ needexit = 1;
+ ast_mutex_unlock(&s->__lock);
+ if (needexit)
+ break;
+ if (s->fd > 0) {
+ if (ast_wait_for_input(s->fd, 1000))
+ break;
+ } else {
+ sleep(1);
+ }
+ }
+ ast_log(LOG_DEBUG, "Finished waiting for an event!\n");
+ ast_mutex_lock(&s->__lock);
+ if (s->waiting_thread == pthread_self()) {
+ astman_send_response(s, m, "Success", "Waiting for Event...");
+ /* Only show events if we're the most recent waiter */
+ while(s->eventq) {
+ astman_append(s, "%s", s->eventq->eventdata);
+ eqe = s->eventq;
+ s->eventq = s->eventq->next;
+ free(eqe);
+ }
+ astman_append(s,
+ "Event: WaitEventComplete\r\n"
+ "%s"
+ "\r\n",idText);
+ s->waiting_thread = AST_PTHREADT_NULL;
+ } else {
+ ast_log(LOG_DEBUG, "Abandoning event request!\n");
+ }
+ ast_mutex_unlock(&s->__lock);
return 0;
}
@@ -1338,10 +1610,10 @@
s->authenticated = 1;
if (option_verbose > 1) {
if ( displayconnects ) {
- ast_verbose(VERBOSE_PREFIX_2 "Manager '%s' logged on from %s\n", s->username, ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr));
+ ast_verbose(VERBOSE_PREFIX_2 "%sManager '%s' logged on from %s\n", (s->sessiontimeout ? "HTTP " : ""), s->username, ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr));
}
}
- ast_log(LOG_EVENT, "Manager '%s' logged on from %s\n", s->username, ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr));
+ ast_log(LOG_EVENT, "%sManager '%s' logged on from %s\n", (s->sessiontimeout ? "HTTP " : ""), s->username, ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr));
astman_send_ack(s, m, "Authentication accepted");
}
} else if (!strcasecmp(action, "Logoff")) {
@@ -1353,7 +1625,7 @@
int ret=0;
struct eventqent *eqe;
ast_mutex_lock(&s->__lock);
- s->busy = 1;
+ s->busy++;
ast_mutex_unlock(&s->__lock);
while( tmp ) {
if (!strcasecmp(action, tmp->action)) {
@@ -1370,15 +1642,17 @@
if (!tmp)
astman_send_error(s, m, "Invalid/unknown command");
ast_mutex_lock(&s->__lock);
- s->busy = 0;
- while(s->eventq) {
- if (ast_carefulwrite(s->fd, s->eventq->eventdata, strlen(s->eventq->eventdata), s->writetimeout) < 0) {
- ret = -1;
- break;
+ if (s->fd > -1) {
+ s->busy--;
+ while(s->eventq) {
+ if (ast_carefulwrite(s->fd, s->eventq->eventdata, strlen(s->eventq->eventdata), s->writetimeout) < 0) {
+ ret = -1;
+ break;
+ }
+ eqe = s->eventq;
+ s->eventq = s->eventq->next;
+ free(eqe);
}
- eqe = s->eventq;
- s->eventq = s->eventq->next;
- free(eqe);
}
ast_mutex_unlock(&s->__lock);
return ret;
@@ -1484,17 +1758,48 @@
int as;
struct sockaddr_in sin;
socklen_t sinlen;
- struct mansession *s;
+ struct mansession *s, *prev=NULL, *next;
struct protoent *p;
int arg = 1;
int flags;
pthread_attr_t attr;
+ time_t now;
+ struct pollfd pfds[1];
+ char iabuf[INET_ADDRSTRLEN];
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
for (;;) {
+ time(&now);
+ ast_mutex_lock(&sessionlock);
+ prev = NULL;
+ s = sessions;
+ while(s) {
+ next = s->next;
+ if (s->sessiontimeout && (now > s->sessiontimeout) && !s->inuse) {
+ if (prev)
+ prev->next = next;
+ else
+ sessions = next;
+ if (s->authenticated && (option_verbose > 1) && displayconnects) {
+ ast_verbose(VERBOSE_PREFIX_2 "HTTP Manager '%s' timed out from %s\n",
+ s->username, ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr));
+ }
+ free_session(s);
+ } else
+ prev = s;
+ s = next;
+ }
+ ast_mutex_unlock(&sessionlock);
+
sinlen = sizeof(sin);
+ pfds[0].fd = asock;
+ pfds[0].events = POLLIN;
+ /* Wait for something to happen, but timeout every few seconds so
+ we can ditch any old manager sessions */
+ if (poll(pfds, 1, 5000) < 1)
+ continue;
as = accept(asock, (struct sockaddr *)&sin, &sinlen);
if (as < 0) {
ast_log(LOG_NOTICE, "Accept returned -1: %s\n", strerror(errno));
@@ -1514,6 +1819,7 @@
memset(s, 0, sizeof(struct mansession));
memcpy(&s->sin, &sin, sizeof(sin));
s->writetimeout = 100;
+ s->waiting_thread = AST_PTHREADT_NULL;
if(! block_sockets) {
/* For safety, make sure socket is non-blocking */
@@ -1593,7 +1899,9 @@
ast_mutex_lock(&s->__lock);
if (s->busy) {
append_event(s, tmp);
- } else if (!s->dead) {
+ if (s->waiting_thread != AST_PTHREADT_NULL)
+ pthread_kill(s->waiting_thread, SIGURG);
+ } else if (!s->dead && !s->sessiontimeout) {
if (ast_carefulwrite(s->fd, tmp, tmp_next - tmp, s->writetimeout) < 0) {
ast_log(LOG_WARNING, "Disconnecting slow (or gone) manager session!\n");
s->dead = 1;
@@ -1701,7 +2009,211 @@
/*! @}
END Doxygen group */
+static struct mansession *find_session(unsigned long ident)
+{
+ struct mansession *s;
+ ast_mutex_lock(&sessionlock);
+ s = sessions;
+ while(s) {
+ ast_mutex_lock(&s->__lock);
+ if (s->sessiontimeout && (s->managerid == ident) && !s->needdestroy) {
+ s->inuse++;
+ break;
+ }
+ ast_mutex_unlock(&s->__lock);
+ s = s->next;
+ }
+ ast_mutex_unlock(&sessionlock);
+ return s;
+}
+
+
+static void vars2msg(struct message *m, struct ast_variable *vars)
+{
+ int x;
+ for (x=0;vars && (x<AST_MAX_MANHEADERS);x++,vars = vars->next) {
+ if (!vars)
+ break;
+ m->hdrcount = x + 1;
+ snprintf(m->headers[x], sizeof(m->headers[x]), "%s: %s", vars->name, vars->value);
+ }
+}
+
+#define FORMAT_RAW 0
+#define FORMAT_HTML 1
+#define FORMAT_XML 2
+
+static char *contenttype[] = { "plain", "html", "xml" };
+
+static char *generic_http_callback(int format, struct sockaddr_in *requestor, const char *uri, struct ast_variable *params, int *status, char **title, int *contentlength)
+{
+ struct mansession *s=NULL;
+ unsigned long ident=0;
+ char workspace[256];
+ char cookie[128];
+ char iabuf[INET_ADDRSTRLEN];
+ int len = sizeof(workspace);
+ int blastaway = 0;
+ char *c = workspace;
+ char *retval=NULL;
+ struct message m;
+ struct ast_variable *v;
+
+ v = params;
+ while(v) {
+ if (!strcasecmp(v->name, "mansession_id")) {
+ sscanf(v->value, "%lx", &ident);
+ break;
+ }
+ v = v->next;
+ }
+ s = find_session(ident);
+
+ if (!s) {
+ /* Create new session */
+ s = calloc(1, sizeof(struct mansession));
+ memcpy(&s->sin, requestor, sizeof(s->sin));
+ s->fd = -1;
+ s->waiting_thread = AST_PTHREADT_NULL;
+ s->send_events = 0;
+ ast_mutex_init(&s->__lock);
+ ast_mutex_lock(&s->__lock);
+ ast_mutex_lock(&sessionlock);
+ s->inuse = 1;
+ s->managerid = rand() | (unsigned long)s;
+ s->next = sessions;
+ sessions = s;
+ ast_mutex_unlock(&sessionlock);
+ }
+
+ /* Reset HTTP timeout */
+ time(&s->sessiontimeout);
+ s->sessiontimeout += httptimeout;
+ ast_mutex_unlock(&s->__lock);
+
+ memset(&m, 0, sizeof(m));
+ if (s) {
+ char tmp[80];
+ ast_build_string(&c, &len, "Content-type: text/%s\n", contenttype[format]);
+ sprintf(tmp, "%08lx", s->managerid);
+ ast_build_string(&c, &len, "%s\r\n", ast_http_setcookie("mansession_id", tmp, httptimeout, cookie, sizeof(cookie)));
+ if (format == FORMAT_HTML)
+ ast_build_string(&c, &len, "<title>Asterisk™ Manager Test Interface</title>");
+ vars2msg(&m, params);
+ if (format == FORMAT_XML) {
+ ast_build_string(&c, &len, "<ajax-response>\n");
+ } else if (format == FORMAT_HTML) {
+ ast_build_string(&c, &len, "<body bgcolor=\"#ffffff\"><table align=center bgcolor=\"#f1f1f1\" width=\"500\">\r\n");
+ ast_build_string(&c, &len, "<tr><td colspan=\"2\" bgcolor=\"#f1f1ff\"><h1> Manager Tester</h1></td></tr>\r\n");
+ }
+ if (process_message(s, &m)) {
+ if (s->authenticated) {
+ if (option_verbose > 1) {
+ if (displayconnects)
+ ast_verbose(VERBOSE_PREFIX_2 "HTTP Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr));
+ }
+ ast_log(LOG_EVENT, "HTTP Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr));
+ } else {
+ if (option_verbose > 1) {
+ if (displayconnects)
+ ast_verbose(VERBOSE_PREFIX_2 "HTTP Connect attempt from '%s' unable to authenticate\n", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr));
+ }
+ ast_log(LOG_EVENT, "HTTP Failed attempt from %s\n", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr));
+ }
+ s->needdestroy = 1;
+ }
+ if (s->outputstr) {
+ char *tmp;
+ if (format == FORMAT_XML)
+ tmp = xml_translate(s->outputstr, params);
+ else if (format == FORMAT_HTML)
+ tmp = html_translate(s->outputstr);
+ else
+ tmp = s->outputstr;
+ if (tmp) {
+ retval = malloc(strlen(workspace) + strlen(tmp) + 128);
+ if (retval) {
+ strcpy(retval, workspace);
+ strcpy(retval + strlen(retval), tmp);
+ c = retval + strlen(retval);
+ len = 120;
+ }
+ free(tmp);
+ }
+ if (tmp != s->outputstr)
+ free(s->outputstr);
+ s->outputstr = NULL;
+ }
+ /* Still okay because c would safely be pointing to workspace even
+ if retval failed to allocate above */
+ if (format == FORMAT_XML) {
+ ast_build_string(&c, &len, "</ajax-response>\n");
+ } else if (format == FORMAT_HTML)
+ ast_build_string(&c, &len, "</table></body>\r\n");
+ } else {
+ *status = 500;
+ *title = strdup("Server Error");
+ }
+ ast_mutex_lock(&s->__lock);
+ if (s->needdestroy) {
+ if (s->inuse == 1) {
+ ast_log(LOG_DEBUG, "Need destroy, doing it now!\n");
+ blastaway = 1;
+ } else {
+ ast_log(LOG_DEBUG, "Need destroy, but can't do it yet!\n");
+ if (s->waiting_thread != AST_PTHREADT_NULL)
+ pthread_kill(s->waiting_thread, SIGURG);
+ s->inuse--;
+ }
+ } else
+ s->inuse--;
+ ast_mutex_unlock(&s->__lock);
+
+ if (blastaway)
+ destroy_session(s);
+ if (*status != 200)
+ return ast_http_error(500, "Server Error", NULL, "Internal Server Error (out of memory)\n");
+ return retval;
+}
+
+static char *manager_http_callback(struct sockaddr_in *requestor, const char *uri, struct ast_variable *params, int *status, char **title, int *contentlength)
+{
+ return generic_http_callback(FORMAT_HTML, requestor, uri, params, status, title, contentlength);
+}
+
+static char *mxml_http_callback(struct sockaddr_in *requestor, const char *uri, struct ast_variable *params, int *status, char **title, int *contentlength)
+{
+ return generic_http_callback(FORMAT_XML, requestor, uri, params, status, title, contentlength);
+}
+
+static char *rawman_http_callback(struct sockaddr_in *requestor, const char *uri, struct ast_variable *params, int *status, char **title, int *contentlength)
+{
+ return generic_http_callback(FORMAT_RAW, requestor, uri, params, status, title, contentlength);
+}
+
+struct ast_http_uri rawmanuri = {
+ .description = "Raw HTTP Manager Event Interface",
+ .uri = "rawman",
+ .has_subtree = 0,
+ .callback = rawman_http_callback,
+};
+
+struct ast_http_uri manageruri = {
+ .description = "HTML Manager Event Interface",
+ .uri = "manager",
+ .has_subtree = 0,
+ .callback = manager_http_callback,
+};
+
+struct ast_http_uri managerxmluri = {
+ .description = "XML Manager Event Interface",
+ .uri = "mxml",
+ .has_subtree = 0,
+ .callback = mxml_http_callback,
+};
+
static int registered = 0;
+static int webregged = 0;
int init_manager(void)
{
@@ -1710,6 +2222,9 @@
int oldportno = portno;
static struct sockaddr_in ba;
int x = 1;
+ int flags;
+ int webenabled=0;
+ int newhttptimeout = 60;
if (!registered) {
/* Register default actions */
ast_manager_register2("Ping", 0, action_ping, "Keepalive command", mandescr_ping);
@@ -1727,6 +2242,7 @@
ast_manager_register2("MailboxStatus", EVENT_FLAG_CALL, action_mailboxstatus, "Check Mailbox", mandescr_mailboxstatus );
ast_manager_register2("MailboxCount", EVENT_FLAG_CALL, action_mailboxcount, "Check Mailbox Message Count", mandescr_mailboxcount );
ast_manager_register2("ListCommands", 0, action_listcommands, "List available manager commands", mandescr_listcommands);
+ ast_manager_register2("WaitEvent", 0, action_waitevent, "Wait for an event to occur", mandescr_waitevent);
ast_cli_register(&show_mancmd_cli);
ast_cli_register(&show_mancmds_cli);
@@ -1750,6 +2266,10 @@
if(val)
block_sockets = ast_true(val);
+ val = ast_variable_retrieve(cfg, "general", "webenabled");
+ if (val)
+ webenabled = ast_true(val);
+
if ((val = ast_variable_retrieve(cfg, "general", "port"))) {
if (sscanf(val, "%d", &portno) != 1) {
ast_log(LOG_WARNING, "Invalid port number '%s'\n", val);
@@ -1762,6 +2282,9 @@
if ((val = ast_variable_retrieve(cfg, "general", "timestampevents")))
timestampevents = ast_true(val);
+
+ if ((val = ast_variable_retrieve(cfg, "general", "httptimeout")))
+ newhttptimeout = atoi(val);
ba.sin_family = AF_INET;
ba.sin_port = htons(portno);
@@ -1785,6 +2308,25 @@
}
ast_config_destroy(cfg);
+ if (webenabled && enabled) {
+ if (!webregged) {
+ ast_http_uri_link(&rawmanuri);
+ ast_http_uri_link(&manageruri);
[... 2390 lines stripped ...]
More information about the asterisk-commits
mailing list