diff --git a/README.md b/README.md index 2316bc9..4a75a70 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## amiws - Asterisk Manager Iterface (AMI) to web-socket proxy +## amiws - Asterisk Manager Interface (AMI) to web-socket proxy [![Build Status](https://travis-ci.org/staskobzar/amiws.svg?branch=master)](https://travis-ci.org/staskobzar/amiws) ![](https://img.shields.io/badge/license-GPL_3-green.svg "License") @@ -7,7 +7,7 @@ ### Introduction -*amiws* is simple proxy from AMI to WEB. It can connect to one or more Asterisk PBXs via AMI (Asterisk Manager Interface), read messages from AMI stream and send actions/commands to it. Received messages are parsed and converted to JSON. +*amiws* is simple proxy from AMI to WEB. It can connect to one or more Asterisk PBXs via AMI (Asterisk Manager Interface), read messages from AMI stream and send actions/commands to it. Received messages are parsed and converted to JSON. *amiws* also provides HTTP/WebSocket interface and sends JSON messages to all connected users via HTTP. @@ -15,7 +15,7 @@ Here is simple workflow scheme: ![amiws workflow](https://github.com/staskobzar/amiws/blob/master/doc/amiws.workflaw.png) -*amiws* proxy can help to build interctive, realtime dashboards for Asterisk PBX single or multiple servers. The example of a simple dashboard can be found in directory "web_root". Here is how it would look like when connecting two Asterisk PBXs: +*amiws* proxy can help to build interactive, real-time dashboards for Asterisk PBX single or multiple servers. The example of a simple dashboard can be found in directory "web_root". Here is how it would look like when connecting two Asterisk PBXs: ![web interface](https://github.com/staskobzar/amiws/blob/master/doc/amiws.user.screen.png) @@ -24,8 +24,8 @@ Here is simple workflow scheme: * Plain TCP or SSL/TLS connection to AMI * Logging with syslog * HTTP and WebSocket server -* SSL/TLS encypted connection for HTTP and WebSocket -* WWW Digest authenticatation with username/password for HTTP(s) +* SSL/TLS encrypted connection for HTTP and WebSocket +* WWW Digest authentication with username/password for HTTP(s) * YAML configuration file @@ -47,12 +47,12 @@ Options: *amiws* relies on several greate projects: * [mongoose](https://github.com/cesanta/mongoose) - awesome embedded networking library from [Cesanta](https://www.cesanta.com/). -* [fozen](https://github.com/cesanta/frozen) - awesome JSON parser and emitter from [Cesanta](https://www.cesanta.com/). -* [re2c](http://re2c.org/) - awesome lexer generator for AMI protocol implementation. +* [frozen](https://github.com/cesanta/frozen) - awesome JSON parser and emitter from [Cesanta](https://www.cesanta.com/). +* [re2c](http://re2c.org/) - awesome lexer generator for AMI protocol implementation. * [cmocka](https://cmocka.org/) - awesome unit testing framework for C. * [lemon](http://www.hwaci.com/sw/lemon/lemon.html) - parser generator to process YAML tokens -They do not need to me installed. *mongoose* and *frozen* are already included. *re2c* and *cmocka* are only needed for developers. +They do not need to be installed. *mongoose* and *frozen* are already included. *re2c* and *cmocka* are only needed for developers. ### Building and install ``` @@ -79,12 +79,12 @@ To run unit tests (requires cmocka): make check ``` -This repo also provides init scripts for [System V](https://github.com/staskobzar/amiws/blob/master/etc/amiws.sysv.init) +This repo also provides init scripts for [System V](https://github.com/staskobzar/amiws/blob/master/etc/amiws.sysv.init) and [systemctl](https://github.com/staskobzar/amiws/blob/master/etc/amiws.service) in "etc/" directory. ### Configuration -Program behaviour is controlled by configuration file. Configuration parameters are described in sample file ["amiws.annotated.yaml"](https://github.com/staskobzar/amiws/blob/master/etc/amiws.annotated.yaml) in directory "etc" of this repository. +Program behaviour is controlled by configuration file. Configuration parameters are described in sample file ["amiws.annotated.yaml"](https://github.com/staskobzar/amiws/blob/master/etc/amiws.annotated.yaml) in directory "etc" of this repository. ### JSON message @@ -137,23 +137,36 @@ _AMI message types_: AMI description in details can be found in [Asterisk wiki](https://wiki.asterisk.org/wiki/display/AST/Home). -*amiws* also accepts JSON messages and send them back to all Asterisk servers: +*amiws* also accepts JSON messages and sends them back to all Asterisk servers: ```javascript sock.send(JSON.stringify({"Action": "CoreStatus", "ActionID": "12345"})); ``` Beware that this will send same Action to all AMI servers! -Keep this in mind when you deploy *amiws* within public Internet and protect access to it. +Keep this in mind when you deploy *amiws* on public Internet and protect access to it. #### Special header AMIServerID To send message to specified AMI server you can use header ```AMIServerID```. -The is should correspond to the ```server_id``` in requests. +This should correspond to the ```server_id``` in requests. Example: ```javascript sock.send(JSON.stringify({"Action": "CoreStatus", "AMIServerID": 1})); ``` +#### Sending multiple variables +In some cases, you will need to send multiple variables. This can be done with a list: + +Example: +```javascript +sock.send(JSON.stringify({"Action": "CoreStatus", "Variable": ["Trunk=provider07", "Hop=hop315"]})); +``` + +List is unnecessary when there is only one variable: +```javascript +sock.send(JSON.stringify({"Action": "CoreStatus", "Variable": "Trunk=provider07"})); +``` + ### SSL/TLS transport SSL/TLS transport usage is described in this blog article: http://staskobzar.blogspot.ca/2017/05/amiws-asterisk-manager-iterface-to-web.html @@ -161,9 +174,9 @@ http://staskobzar.blogspot.ca/2017/05/amiws-asterisk-manager-iterface-to-web.htm Some more information can be found in annotated configuration file. ### Digest Authentication -HTTP content can be protected with username/password. When this option is enabled, then anyone trying access web-page, will have to provide username and password to proceed. +HTTP content can be protected with username/password. When this option is enabled, anyone trying to access web-page will have to provide username and password to proceed. -Configuration paramers to set are: +Configuration parameters to set are: ``` auth_domain : example.com auth_file : /var/www/.htaccess diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 54b7d7e..d58357f 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -1,7 +1,7 @@ noinst_LIBRARIES = libmongoose.a libfrozen.a libamipack.a libamiws.a libamiwscfg.a libamiwscfg_a_SOURCES = config.c config_parser.c config_parser.h amiws.h -libamiws_a_SOURCES = amiws_lib.c amiws.h +libamiws_a_SOURCES = amiws_lib.c amiws.h linked_str_stack.c linked_str_stack.h #libmongoose_a_CPPFLAGS = -DMG_ENABLE_DEBUG libmongoose_a_SOURCES= mongoose.c mongoose.h libfrozen_a_SOURCES = frozen.c frozen.h diff --git a/src/lib/amipack.h b/src/lib/amipack.h index be0a372..9787dec 100644 --- a/src/lib/amipack.h +++ b/src/lib/amipack.h @@ -19,7 +19,7 @@ */ /** - * @file amiws.h + * @file amipack.h * @brief AMI (Asterisk Management Interface) messages * read/create functions interface. * AMI packet is implemented as linked list of headers. diff --git a/src/lib/amiws_lib.c b/src/lib/amiws_lib.c index 63643d8..ee8f561 100644 --- a/src/lib/amiws_lib.c +++ b/src/lib/amiws_lib.c @@ -26,6 +26,7 @@ */ #include "amiws.h" +#include "linked_str_stack.h" static struct mg_mgr mgr; static struct mg_serve_http_opts s_http_server_opts; @@ -384,6 +385,10 @@ int scan_amipack( const char *p, return found ? i + 3 : 0; } +typedef struct CallbackData_ { + AMIPacket *pack; + LinkedStrStack *stack; +} CallbackData; static void send_ami_action(struct websocket_message *wm, struct mg_connection *nc) @@ -391,14 +396,21 @@ static void send_ami_action(struct websocket_message *wm, char *p_str = NULL; size_t len; struct mg_connection *c; - AMIPacket *pack = (AMIPacket *) amipack_init (); + AMIPacket *pack = amipack_init(); + + CallbackData callback_data; + callback_data.pack = pack; + callback_data.stack = linked_str_stack_create(); - if(json_walk((const char*)wm->data, wm->size, json_scan_cb, (void*)pack) < (int)wm->size ){ - syslog (LOG_ERR, "Invalid JSON string: %.*s", (int)wm->size, wm->data); + if (json_walk((const char *) wm->data, wm->size, json_scan_cb, (void *) &callback_data) < (int) wm->size) { + syslog(LOG_ERR, "Invalid JSON string: %.*s", (int) wm->size, wm->data); amipack_destroy(pack); + linked_str_stack_destroy(callback_data.stack); return; } + linked_str_stack_destroy(callback_data.stack); + len = amipack_to_str(pack, &p_str); syslog (LOG_DEBUG, "Converted to AMI packet from JSON:\n %.*s", (int)len, p_str); @@ -428,7 +440,13 @@ static void json_scan_cb( void *callback_data, const char *path, const struct json_token *token) { - AMIPacket *pack = (AMIPacket*) callback_data; + char *hdr_name; + size_t name_size; + + CallbackData *data = (CallbackData *) callback_data; + AMIPacket *pack = data->pack; + LinkedStrStack *stack = data->stack; + switch(token->type) { case JSON_TYPE_STRING: case JSON_TYPE_NUMBER: @@ -439,10 +457,28 @@ static void json_scan_cb( void *callback_data, pack->sid = atoi(token->ptr); syslog (LOG_DEBUG, "AMI server header AMIServerID: %d", pack->sid); } else { - amipack_append(pack, strndup(name, name_len), name_len, - strndup(token->ptr, token->len), token->len); + name_size = linked_str_stack_peek(stack, &hdr_name); + if(hdr_name != NULL) + hdr_name = strndup(hdr_name, name_size); + else { + hdr_name = strndup(name, name_len); + name_size = name_len; + } + + amipack_append(pack, hdr_name, name_size, + strndup(token->ptr, token->len), token->len); } break; + case JSON_TYPE_ARRAY_START: + linked_str_stack_push(stack, strndup(name, name_len), name_len); + break; + case JSON_TYPE_OBJECT_START: + linked_str_stack_push(stack, NULL, 0); + break; + case JSON_TYPE_OBJECT_END: + case JSON_TYPE_ARRAY_END: + linked_str_stack_pull(stack); + break; default: break; } } diff --git a/src/lib/linked_str_stack.c b/src/lib/linked_str_stack.c new file mode 100644 index 0000000..33f083c --- /dev/null +++ b/src/lib/linked_str_stack.c @@ -0,0 +1,104 @@ +/** + * amiws -- Library with functions for read/create AMI packets + * Copyright (C) 2017, Stas Kobzar + * + * This file is part of amiws. + * + * amiws is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * amiws is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with amiws. If not, see . + */ + +/** + * @file linked_str_stack.c + * @brief Linked stack of strings implementation + * + * @author Kohanis + */ + +#include +#include + +#include "linked_str_stack.h" + +LinkedStrStackNode *linked_str_stack_node_create(char *str, size_t len, LinkedStrStackNode* prev) +{ + LinkedStrStackNode *node = (LinkedStrStackNode *)malloc(sizeof(LinkedStrStackNode)); + assert ( node != NULL && "Failed to allocate memory for linked stack node"); + + node->str = str; + node->len = len; + node->prev = prev; + + return node; +} + +void linked_str_stack_node_destroy(LinkedStrStackNode* node) +{ + if (node == NULL) + return; + + if(node->str) + free(node->str); + + free(node); + node = NULL; +} + +LinkedStrStack *linked_str_stack_create() +{ + LinkedStrStack *stack = (LinkedStrStack *)malloc(sizeof(LinkedStrStack)); + assert (stack != NULL && "Failed to allocate memory for linked stack"); + + stack->top = NULL; + return stack; +} + +void linked_str_stack_destroy(LinkedStrStack *stack) +{ + if(stack == NULL) + return; + + LinkedStrStackNode *prev, *current = stack->top; + while(current != NULL) { + prev = current->prev; + linked_str_stack_node_destroy(current); + current = prev; + } + + free(stack); + stack = NULL; +} + +void linked_str_stack_push(LinkedStrStack *stack, char *str, size_t len) +{ + stack->top = linked_str_stack_node_create(str, len, stack->top); +} + +size_t linked_str_stack_peek(LinkedStrStack *stack, char **str) +{ + if(stack->top == NULL) { + *str = NULL; + return 0; + } + *str = stack->top->str; + return stack->top->len; +} + +void linked_str_stack_pull(LinkedStrStack *stack) +{ + if(stack->top == NULL) + return; + LinkedStrStackNode *prev = stack->top->prev; + linked_str_stack_node_destroy(stack->top); + stack->top = prev; +} diff --git a/src/lib/linked_str_stack.h b/src/lib/linked_str_stack.h new file mode 100644 index 0000000..5192d1d --- /dev/null +++ b/src/lib/linked_str_stack.h @@ -0,0 +1,55 @@ +/** + * amiws -- Library with functions for read/create AMI packets + * Copyright (C) 2017, Stas Kobzar + * + * This file is part of amiws. + * + * amiws is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * amiws is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with amiws. If not, see . + */ + +/** + * @file linked_str_stack.h + * @brief Linked stack of strings implementation interface + * + * @author Kohanis + */ + +#ifndef __AMIWS_linked_str_stack_H +#define __AMIWS_linked_str_stack_H + +#include + +typedef struct LinkedStrStackNode_ { + char *str; + size_t len; + + struct LinkedStrStackNode_ *prev; +} LinkedStrStackNode; + +typedef struct LinkedStrStack_ { + LinkedStrStackNode *top; +} LinkedStrStack; + + +LinkedStrStack *linked_str_stack_create(); + +void linked_str_stack_destroy(LinkedStrStack *stack); + +void linked_str_stack_push(LinkedStrStack *stack, char *str, size_t len); + +size_t linked_str_stack_peek(LinkedStrStack *stack, char **str); + +void linked_str_stack_pull(LinkedStrStack *stack); + +#endif