You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kamailio/modules/xhttp_rpc/xhttp_rpc.c

816 lines
20 KiB

/*
* Copyright (C) 2011 VoIP Embedded, Inc.
*
* This file is part of Kamailio, a free SIP server.
*
* Kamailio 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 2 of the License, or
* (at your option) any later version
*
* Kamailio 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include "../../ver.h"
#include "../../trim.h"
#include "../../sr_module.h"
#include "../../nonsip_hooks.h"
#include "../../modules/xhttp/api.h"
#include "xhttp_rpc.h"
#include "xhttp_rpc_fnc.h"
/** @addtogroup xhttp_rpc
* @ingroup modules
* @{
*
* <h1>Overview of Operation</h1>
* This module provides a web interface for RPC management interface.
* It is built on top of the xhttp API module.
*/
/** @file
*
* This is the main file of xhttp_rpc module which contains all the functions
* related to http processing, as well as the module interface.
*/
MODULE_VERSION
str XHTTP_RPC_REASON_OK = str_init("OK");
str XHTTP_RPC_CONTENT_TYPE_TEXT_HTML = str_init("text/html");
xhttp_rpc_mod_cmds_t *xhttp_rpc_mod_cmds = NULL;
int xhttp_rpc_mod_cmds_size = 0;
/* FIXME: this should be initialized in ../../ver.c */
int full_version_len;
int ver_name_len;
static int mod_init(void);
static int child_init(int rank);
static int xhttp_rpc_dispatch(sip_msg_t* msg, char* s1, char* s2);
/** The context of the xhttp_rpc request being processed.
*
* This is a global variable that records the context of the xhttp_rpc request
* being currently processed.
* @sa rpc_ctx
*/
static rpc_ctx_t ctx;
static xhttp_api_t xhttp_api;
/** Pointers to the functions that implement the RPC interface
* of xhttp_rpc module
*/
static rpc_t func_param;
str xhttp_rpc_root = str_init("rpc");
int buf_size = 0;
char error_buf[ERROR_REASON_BUF_LEN];
static cmd_export_t cmds[] = {
{"dispatch_xhttp_rpc",(cmd_function)xhttp_rpc_dispatch,0,0,0,REQUEST_ROUTE},
{0, 0, 0, 0, 0, 0}
};
static param_export_t params[] = {
{"xhttp_rpc_root", PARAM_STR, &xhttp_rpc_root},
{"xhttp_rpc_buf_size", INT_PARAM, &buf_size},
{0, 0, 0}
};
/** module exports */
struct module_exports exports= {
"xhttp_rpc",
DEFAULT_DLFLAGS, /* dlopen flags */
cmds,
params,
0, /* exported statistics */
0, /* exported MI functions */
0, /* exported pseudo-variables */
0, /* extra processes */
mod_init, /* module initialization function */
0,
0,
child_init /* per-child init function */
};
/** Implementation of rpc_fault function required by the management API.
*
* This function will be called whenever a management function
* indicates that an error ocurred while it was processing the request. The
* function takes the reply code and reason phrase as parameters, these will
* be put in the body of the reply.
*
* @param ctx A pointer to the context structure of the request being
* processed.
* @param code Reason code.
* @param fmt Formatting string used to build the reason phrase.
*/
static void rpc_fault(rpc_ctx_t* ctx, int code, char* fmt, ...)
{
va_list ap;
struct xhttp_rpc_reply *reply = &ctx->reply;
reply->code = code;
va_start(ap, fmt);
vsnprintf(error_buf, ERROR_REASON_BUF_LEN, fmt, ap);
va_end(ap);
reply->reason.len = strlen(error_buf);
reply->reason.s = error_buf;
/* reset body so we can print the error */
reply->body.len = 0;
return;
}
/**
*/
static void free_data_struct(struct rpc_data_struct *rpc_d)
{
struct rpc_data_struct *ds;
if (!rpc_d) {
LM_ERR("Atempting to free NULL rpc_data_struct\n");
return;
}
while (rpc_d) {
ds = rpc_d->next;
pkg_free(rpc_d);
rpc_d = ds;
}
return;
}
/**
*/
static struct rpc_data_struct *new_data_struct(rpc_ctx_t* ctx)
{
struct rpc_data_struct *ds;
if (!ctx) return NULL;
ds = (struct rpc_data_struct*)pkg_malloc(sizeof(struct rpc_data_struct));
if (!ds) {
rpc_fault(ctx, 500, "Internal Server Error (oom)");
return NULL;
}
memset(ds, 0, sizeof(struct rpc_data_struct));
ds->ctx = ctx;
return ds;
}
/** Initialize xhttp_rpc reply data structure.
*
* This function initializes the data structure that contains all data related
* to the xhttp_rpc reply being created. The function must be called before any
* other function that adds data to the reply.
* @param ctx rpc_ctx_t structure to be initialized.
* @return 0 on success, a negative number on error.
*/
static int init_xhttp_rpc_reply(rpc_ctx_t *ctx)
{
struct xhttp_rpc_reply *reply = &ctx->reply;
reply->code = 200;
reply->reason = XHTTP_RPC_REASON_OK;
reply->buf.s = pkg_malloc(buf_size);
if (!reply->buf.s) {
LM_ERR("oom\n");
rpc_fault(ctx, 500, "Internal Server Error (No memory left)");
return -1;
}
reply->buf.len = buf_size;
reply->body.s = reply->buf.s;
reply->body.len = 0;
return 0;
}
/** Implementation of rpc_send function required by the management API.
*
* This is the function that will be called whenever a management function
* asks the management interface to send the reply to the client.
* The SIP/HTTP reply sent to
* the client will be always 200 OK, if an error ocurred on the server then it
* will be indicated in the html document in body.
*
* @param ctx A pointer to the context structure of the xhttp_rpc request that
* generated the reply.
* @return 1 if the reply was already sent, 0 on success, a negative number on
* error
*/
static int rpc_send(rpc_ctx_t* ctx)
{
struct xhttp_rpc_reply* reply;
if (ctx->reply_sent) return 1;
reply = &ctx->reply;
if (0!=xhttp_rpc_build_page(ctx)){
rpc_fault(ctx, 500, "Internal Server Error");
}
ctx->reply_sent = 1;
if (reply->body.len)
xhttp_api.reply(ctx->msg, reply->code, &reply->reason,
&XHTTP_RPC_CONTENT_TYPE_TEXT_HTML, &reply->body);
else
xhttp_api.reply(ctx->msg, reply->code, &reply->reason,
&XHTTP_RPC_CONTENT_TYPE_TEXT_HTML, &reply->reason);
if (reply->buf.s) {
pkg_free(reply->buf.s);
reply->buf.s = NULL;
reply->buf.len = 0;
}
if (ctx->arg.s) {
pkg_free(ctx->arg.s);
ctx->arg.s = NULL;
ctx->arg.len = 0;
}
if (ctx->data_structs) {
free_data_struct(ctx->data_structs);
ctx->data_structs = NULL;
}
return 0;
}
/** Converts the variables provided in parameter ap according to formatting
* string provided in parameter fmt into HTML format.
*
* This function takes the parameters provided in ap parameter and creates
* HTML formatted parameters that will be put in the html document.
* The format of input parameters is described in formatting string
* fmt which follows the syntax of the management API. In the case of
* an error the function will generate an error reply in err_reply parameter
* instead.
* @param ctx An error reply document will be generated here if the
* function encounters a problem while processing input
* parameters.
* @param fmt Formatting string of the management API.
* @param ap A pointer to the array of input parameters.
*
*/
static int print_value(rpc_ctx_t* ctx, char fmt, va_list* ap, str *id)
{
str body;
str *sp;
char buf[PRINT_VALUE_BUF_LEN];
time_t dt;
struct tm* t;
switch(fmt) {
case 'd':
body.s = sint2str(va_arg(*ap, int), &body.len);
break;
case 'f':
body.s = buf;
body.len = snprintf(buf, PRINT_VALUE_BUF_LEN,
"%f", va_arg(*ap, double));
if (body.len < 0) {
LM_ERR("Error while converting double\n");
return -1;
}
break;
case 'b':
body.len = 1;
body.s = ((va_arg(*ap, int)==0)?"0":"1");
break;
case 't':
body.s = buf;
body.len = sizeof("19980717T14:08:55") - 1;
dt = va_arg(*ap, time_t);
t = gmtime(&dt);
if (strftime(buf, PRINT_VALUE_BUF_LEN,
"%Y%m%dT%H:%M:%S", t) == 0) {
LM_ERR("Error while converting time\n");
return -1;
}
break;
case 's':
body.s = va_arg(*ap, char*);
body.len = strlen(body.s);
break;
case 'S':
sp = va_arg(*ap, str*);
body = *sp;
break;
default:
body.len = 0;
body.s = NULL;
LM_ERR("Invalid formatting character [%c]\n", fmt);
return -1;
}
if (0!=xhttp_rpc_build_content(ctx, &body, id)) {
rpc_fault(ctx, 500, "Internal Server Error");
return -1;
}
return 0;
}
/** Implementation of rpc_add function required by the management API.
*
* This function will be called when an RPC management function calls
* rpc->add to add a parameter to the xhttp_rpc reply being generated.
*/
static int rpc_add(rpc_ctx_t* ctx, char* fmt, ...)
{
void **void_ptr;
struct rpc_data_struct *ds;
va_list ap;
if (0!=xhttp_rpc_build_content(ctx, NULL, NULL)) {
rpc_fault(ctx, 500, "Internal Server Error");
return -1;
}
va_start(ap, fmt);
while(*fmt) {
if (*fmt == '{' || *fmt == '[') {
void_ptr = va_arg(ap, void**);
ds = new_data_struct(ctx);
if (!ds) goto err;
if (ctx->data_structs) free_data_struct(ctx->data_structs);
ctx->data_structs = ds;
*void_ptr = ds;
} else {
if (print_value(ctx, *fmt, &ap, NULL) < 0) goto err;
}
fmt++;
}
va_end(ap);
return 0;
err:
va_end(ap);
return -1;
}
/** Implementation of rpc->scan function required by the management API.
*
* This is the function that will be called whenever a management function
* calls rpc->scan to get the value of parameter from the xhttp_rpc
* request. This function will extract the current parameter from the xhttp_rpc
* URL and attempts to convert it to the type requested by the management
* function that called it.
*/
static int rpc_scan(rpc_ctx_t* ctx, char* fmt, ...)
{
int *int_ptr;
char **char_ptr;
double *double_ptr;
str *str_ptr;
str arg;
int mandatory_param = 1;
int modifiers = 0;
char* orig_fmt;
va_list ap;
orig_fmt=fmt;
va_start(ap, fmt);
while(*fmt) {
switch(*fmt) {
case '*': /* start of optional parameters */
mandatory_param = 0;
modifiers++;
fmt++;
continue;
break;
case '.': /* autoconvert */
modifiers++;
fmt++;
continue;
break;
case 'b': /* Bool */
case 't': /* Date and time */
case 'd': /* Integer */
xhttp_rpc_get_next_arg(ctx, &arg);
if (arg.len==0)
goto read_error;
int_ptr = va_arg(ap, int*);
*int_ptr = strtol(arg.s, 0, 0);
break;
case 'f': /* double */
xhttp_rpc_get_next_arg(ctx, &arg);
if (arg.len==0)
goto read_error;
double_ptr = va_arg(ap, double*);
*double_ptr = strtod(arg.s, 0);
break;
case 's': /* zero terminated string */
xhttp_rpc_get_next_arg(ctx, &arg);
if (arg.len==0)
goto read_error;
char_ptr = va_arg(ap, char**);
*char_ptr = arg.s;
break;
case 'S': /* str structure */
xhttp_rpc_get_next_arg(ctx, &arg);
if (arg.len==0)
goto read_error;
str_ptr = va_arg(ap, str*);
*str_ptr = arg;
break;
case '{':
xhttp_rpc_get_next_arg(ctx, &arg);
if (arg.len==0)
goto read_error;
LM_ERR("Unsupported param type [{]\n");
rpc_fault(ctx, 500, "Unsupported param type [{]");
goto error;
break;
default:
LM_ERR("Invalid param type in formatting string: [%c]\n", *fmt);
rpc_fault(ctx, 500,
"Internal Server Error (inval formatting str)");
goto error;
}
fmt++;
}
va_end(ap);
return (int)(fmt-orig_fmt)-modifiers;
read_error:
if (mandatory_param) rpc_fault(ctx, 400, "Invalid parameter value");
error:
va_end(ap);
return -((int)(fmt-orig_fmt)-modifiers);
}
/** Implementation of rpc_rpl_printf function required by the management API.
*
* This function will be called whenever an RPC management function calls
* rpc-printf to add a parameter to the xhttp_rpc reply being constructed.
*/
static int rpc_rpl_printf(rpc_ctx_t* ctx, char* fmt, ...)
{
int n, size;
char *p;
va_list ap;
if (0!=xhttp_rpc_build_content(ctx, NULL, NULL)) {
rpc_fault(ctx, 500, "Internal Server Error");
return -1;
}
p = ctx->reply.body.s + ctx->reply.body.len;
size = ctx->reply.buf.len - ctx->reply.body.len;
va_start(ap, fmt);
n = vsnprintf(p, size, fmt, ap);
va_end(ap);
if (n > -1 && n < size) {
ctx->reply.body.len += n;
p += n;
} else {
LM_ERR("oom\n");
rpc_fault(ctx, 500, "Internal Server Error (oom)");
return -1;
}
if (0!=xhttp_rpc_insert_break(ctx)) {
LM_ERR("oom\n");
rpc_fault(ctx, 500, "Internal Server Error (oom)");
return -1;
}
return 0;
}
/** Adds a new member to structure.
*/
static int rpc_struct_add(struct rpc_data_struct* rpc_s, char* fmt, ...)
{
va_list ap;
void **void_ptr;
str member_name;
rpc_ctx_t *ctx = rpc_s->ctx;
struct rpc_data_struct *ds, *s;
if (!ctx) {
LM_ERR("Invalid context\n");
return -1;
}
if (!ctx->data_structs) {
LM_ERR("Invalid structs\n");
return -1;
}
s = ds = ctx->data_structs;
ctx->struc_depth = 0;
while (s) {
if (s == rpc_s) {
if (s->next) {
free_data_struct(s->next);
s->next = NULL;
}
break;
}
ctx->struc_depth++;
ds = s;
s = s->next;
}
if (!s)
s = ds;
va_start(ap, fmt);
while(*fmt) {
member_name.s = va_arg(ap, char*);
member_name.len = (member_name.s?strlen(member_name.s):0);
if (*fmt == '{' || *fmt == '[') {
void_ptr = va_arg(ap, void**);
ds = new_data_struct(ctx);
if (!ds) goto err;
s->next = ds;
*void_ptr = ds;
if (0!=xhttp_rpc_build_content(ctx, NULL, &member_name))
goto err;
} else {
if (print_value(ctx, *fmt, &ap, &member_name) < 0) goto err;
}
fmt++;
}
va_end(ap);
return 0;
err:
va_end(ap);
return -1;
}
static int rpc_struct_scan(void* s, char* fmt, ...)
{
LM_ERR("Not implemented\n");
return -1;
}
/** Create a new member from formatting string and add it to a structure.
*/
static int rpc_struct_printf(void* s, char* member_name, char* fmt, ...)
{
LM_ERR("Not implemented\n");
return -1;
}
/** Returns the RPC capabilities supported by the xmlrpc driver.
*/
static rpc_capabilities_t rpc_capabilities(rpc_ctx_t* ctx)
{
/* No support for async commands.
*/
return 0;
}
/** Returns a new "delayed reply" context.
* Creates a new delayed reply context in shm and returns it.
* @return 0 - not supported, already replied, or no more memory;
* !=0 pointer to the special delayed ctx.
* Note1: one should use the returned ctx reply context to build a reply and
* when finished call rpc_delayed_ctx_close().
* Note2: adding pieces to the reply in different processes is not supported.
*/
static struct rpc_delayed_ctx* rpc_delayed_ctx_new(rpc_ctx_t* ctx)
{
return NULL;
}
/** Closes a "delayed reply" context and sends the reply.
* If no reply has been sent the reply will be built and sent automatically.
* See the notes from rpc_new_delayed_ctx()
*/
static void rpc_delayed_ctx_close(struct rpc_delayed_ctx* dctx)
{
return;
}
static int mod_init(void)
{
int i;
/* bind the XHTTP API */
if (xhttp_load_api(&xhttp_api) < 0) {
LM_ERR("cannot bind to XHTTP API\n");
return -1;
}
/* Check xhttp_rpc_buf_size param */
if (buf_size == 0)
buf_size = pkg_mem_size/3;
/* Check xhttp_rpc_root param */
for(i=0;i<xhttp_rpc_root.len;i++){
if ( !isalnum(xhttp_rpc_root.s[i]) && xhttp_rpc_root.s[i]!='_') {
LM_ERR("bad xhttp_rpc_root param [%.*s], char [%c] "
"- use only alphanumerical chars\n",
xhttp_rpc_root.len, xhttp_rpc_root.s,
xhttp_rpc_root.s[i]);
return -1;
}
}
memset(&func_param, 0, sizeof(func_param));
func_param.send = (rpc_send_f)rpc_send;
func_param.fault = (rpc_fault_f)rpc_fault;
func_param.add = (rpc_add_f)rpc_add;
func_param.scan = (rpc_scan_f)rpc_scan;
func_param.rpl_printf = (rpc_rpl_printf_f)rpc_rpl_printf;
func_param.struct_add = (rpc_struct_add_f)rpc_struct_add;
/* use rpc_struct_add for array_add */
func_param.array_add = (rpc_struct_add_f)rpc_struct_add;
func_param.struct_scan = (rpc_struct_scan_f)rpc_struct_scan;
func_param.struct_printf = (rpc_struct_printf_f)rpc_struct_printf;
func_param.capabilities = (rpc_capabilities_f)rpc_capabilities;
func_param.delayed_ctx_new = (rpc_delayed_ctx_new_f)rpc_delayed_ctx_new;
func_param.delayed_ctx_close =
(rpc_delayed_ctx_close_f)rpc_delayed_ctx_close;
return 0;
}
static int child_init(int rank)
{
int i, j;
int len;
xhttp_rpc_mod_cmds_t *cmds;
/* rpc_export_t *rpc_e; */
if(rank==PROC_MAIN || rank==PROC_TCP_MAIN)
return 0; /* do nothing for the main process */
if (rank==PROC_INIT)
{
/* building a cache of rpc module commands */
xhttp_rpc_mod_cmds =
(xhttp_rpc_mod_cmds_t*)pkg_malloc(sizeof(xhttp_rpc_mod_cmds_t));
if (xhttp_rpc_mod_cmds==NULL){
LM_ERR("oom\n");
return -1;
}
xhttp_rpc_mod_cmds->rpc_e_index = 0;
xhttp_rpc_mod_cmds->mod.s = NULL;
xhttp_rpc_mod_cmds->mod.len = 0;
xhttp_rpc_mod_cmds->size = 0;
xhttp_rpc_mod_cmds_size = 1;
cmds = xhttp_rpc_mod_cmds;
for(i=0; i<rpc_sarray_crt_size; i++){
len = strlen(rpc_sarray[i]->name);
j = 0;
while (j<len && rpc_sarray[i]->name[j]!='.')
j++;
if (j==len) {
LM_DBG("dropping invalid command format [%.*s]\n",
len, rpc_sarray[i]->name);
} else {
if (cmds->mod.len==0) {
/* this is the first module */
cmds->rpc_e_index = i;
cmds->mod.s = (char*)&rpc_sarray[i]->name[0];
cmds->mod.len = j;
cmds->size++;
} else if (cmds->mod.len==j &&
strncmp(cmds->mod.s,
(char*)&rpc_sarray[i]->name[0],
j)==0){
cmds->size++;
} else {
cmds = (xhttp_rpc_mod_cmds_t*)
pkg_realloc(xhttp_rpc_mod_cmds,
(xhttp_rpc_mod_cmds_size+1)*
sizeof(xhttp_rpc_mod_cmds_t));
if (cmds==NULL){
LM_ERR("oom\n");
return -1;
}
xhttp_rpc_mod_cmds = cmds;
cmds = &xhttp_rpc_mod_cmds[xhttp_rpc_mod_cmds_size];
cmds->rpc_e_index = i;
cmds->mod.s = (char*)&rpc_sarray[i]->name[0];
cmds->mod.len = j;
xhttp_rpc_mod_cmds_size++;
cmds->size = 1;
}
}
}
/*
for(i=0; i<xhttp_rpc_mod_cmds_size; i++){
for (j=0; j<xhttp_rpc_mod_cmds[i].size; j++){
rpc_e = rpc_sarray[xhttp_rpc_mod_cmds[i].rpc_e_index+j];
LM_DBG("[%p] => [%p]->[%.*s] [%p]->[%s]\n",
rpc_e,
xhttp_rpc_mod_cmds[i].mod.s,
xhttp_rpc_mod_cmds[i].mod.len,
xhttp_rpc_mod_cmds[i].mod.s,
rpc_e->name,
rpc_e->name);
}
}
*/
}
full_version_len = strlen(full_version);
ver_name_len = strlen(ver_name);
return 0;
}
static int xhttp_rpc_dispatch(sip_msg_t* msg, char* s1, char* s2)
{
rpc_export_t* rpc_e;
str arg = {NULL, 0};
int ret = 0;
int i;
if(!IS_HTTP(msg)) {
LM_DBG("Got non HTTP msg\n");
return NONSIP_MSG_PASS;
}
/* Init xhttp_rpc context */
if (ctx.reply.buf.s) LM_ERR("Unexpected buf value [%p][%d]\n",
ctx.reply.buf.s, ctx.reply.buf.len);
memset(&ctx, 0, sizeof(rpc_ctx_t));
ctx.msg = msg;
ctx.mod = ctx.cmd = -1;
if (init_xhttp_rpc_reply(&ctx) < 0) goto send_reply;
/* Extract arguments from url */
if (0!=xhttp_rpc_parse_url(&msg->first_line.u.request.uri,
&ctx.mod, &ctx.cmd, &arg)){
rpc_fault(&ctx, 500, "Bad URL");
goto send_reply;
}
if (arg.s) {
if (arg.len) {
/* Unescape args */
ctx.arg.s = pkg_malloc((arg.len+1)*sizeof(char));
if (ctx.arg.s==NULL){
LM_ERR("oom\n");
rpc_fault(&ctx, 500, "Internal Server Error (oom)");
goto send_reply;
}
for(i=0;i<arg.len;i++) if (arg.s[i]=='+') arg.s[i]=' ';
if (0>un_escape(&arg, &ctx.arg)) {
LM_ERR("unable to escape [%.*s]\n", arg.len, arg.s);
rpc_fault(&ctx, 500, "Bad arg in URL");
goto send_reply;
}
ctx.arg.s[ctx.arg.len] = '\0';
ctx.arg.len++;
ctx.arg2scan = ctx.arg;
}
ctx.arg_received = 1;
} else {
goto send_reply;
}
/*
rpc_e=find_rpc_export((char*)rpc_sarray[xhttp_rpc_mod_cmds[ctx.mod].rpc_e_index+ctx.cmd]->name, 0);
if ((rpc_e==NULL) || (rpc_e->function==NULL)){
LM_ERR("Unable to find rpc command [%s]\n",
rpc_sarray[xhttp_rpc_mod_cmds[ctx.mod].rpc_e_index+ctx.cmd]->name);
rpc_fault(&ctx, 500, "Method not found");
goto send_reply;
}
*/
rpc_e=rpc_sarray[xhttp_rpc_mod_cmds[ctx.mod].rpc_e_index+ctx.cmd];
rpc_e->function(&func_param, &ctx);
send_reply:
if (!ctx.reply_sent) {
ret = rpc_send(&ctx);
}
if (ret < 0) return -1;
return 0;
}