mirror of https://github.com/sipwise/kamailio.git
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.
1748 lines
44 KiB
1748 lines
44 KiB
/**
|
|
* Copyright (C) 2014 Daniel-Constantin Mierla (asipto.com)
|
|
*
|
|
* 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 <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "../../ver.h"
|
|
#include "../../trim.h"
|
|
#include "../../pt.h"
|
|
#include "../../sr_module.h"
|
|
#include "../../mod_fix.h"
|
|
#include "../../nonsip_hooks.h"
|
|
#include "../../cfg/cfg_struct.h"
|
|
#include "../../modules/xhttp/api.h"
|
|
|
|
#include "jsonrpc-s_mod.h"
|
|
|
|
/** @addtogroup jsonrpc-s
|
|
* @ingroup modules
|
|
* @{
|
|
*
|
|
* <h1>Overview of Operation</h1>
|
|
* This module provides jsonrpc over http server implementation.
|
|
*/
|
|
|
|
/** @file
|
|
*
|
|
* This is the main file of jsonrpc-s module which contains all the functions
|
|
* related to http processing, as well as the module interface.
|
|
*/
|
|
|
|
MODULE_VERSION
|
|
|
|
|
|
#define jsonrpc_malloc pkg_malloc
|
|
#define jsonrpc_free pkg_free
|
|
|
|
static str JSONRPC_REASON_OK = str_init("OK");
|
|
static str JSONRPC_CONTENT_TYPE_HTML = str_init("application/json");
|
|
|
|
/* FIFO server vars */
|
|
static char *jsonrpc_fifo = NULL; /*!< FIFO file name */
|
|
static char *jsonrpc_fifo_reply_dir = "/tmp/"; /*!< dir where reply fifos are allowed */
|
|
static int jsonrpc_fifo_uid = -1; /*!< Fifo default UID */
|
|
static char *jsonrpc_fifo_uid_s = 0; /*!< Fifo default User ID name */
|
|
static int jsonrpc_fifo_gid = -1; /*!< Fifo default Group ID */
|
|
static char *jsonrpc_fifo_gid_s = 0; /*!< Fifo default Group ID name */
|
|
static int jsonrpc_fifo_mode = S_IRUSR| S_IWUSR| S_IRGRP| S_IWGRP; /* Default file mode rw-rw---- */
|
|
|
|
static int jsonrpc_transport = 0; /*!< 0 - all available; 1 - http; 2 - fifo */
|
|
|
|
static int jsonrpc_pretty_format = 0;
|
|
|
|
static int jsonrpc_register_rpc(void);
|
|
|
|
static int mod_init(void);
|
|
static int child_init(int rank);
|
|
static void mod_destroy(void);
|
|
static int jsonrpc_dispatch(sip_msg_t* msg, char* s1, char* s2);
|
|
static int jsonrpc_exec(sip_msg_t* msg, char* cmd, char* s2);
|
|
|
|
static int jsonrpc_init_fifo_file(void);
|
|
static void jsonrpc_fifo_process(int rank);
|
|
|
|
/** The context of the jsonrpc request being processed.
|
|
*
|
|
* This is a global variable that records the context of the jsonrpc request
|
|
* being currently processed.
|
|
* @sa rpc_ctx
|
|
*/
|
|
static jsonrpc_ctx_t _jsonrpc_ctx;
|
|
|
|
static xhttp_api_t xhttp_api;
|
|
|
|
/** Pointers to the functions that implement the RPC interface
|
|
* of jsonrpc module
|
|
*/
|
|
static rpc_t func_param;
|
|
|
|
#define JSONRPC_ERROR_REASON_BUF_LEN 128
|
|
#define JSONRPC_PRINT_VALUE_BUF_LEN 1024
|
|
|
|
char jsonrpc_error_buf[JSONRPC_ERROR_REASON_BUF_LEN];
|
|
|
|
static cmd_export_t cmds[] = {
|
|
{"jsonrpc_dispatch", (cmd_function)jsonrpc_dispatch, 0, 0, 0,
|
|
REQUEST_ROUTE},
|
|
{"jsonrpc_exec", (cmd_function)jsonrpc_exec, 1, fixup_spve_null, 0,
|
|
ANY_ROUTE},
|
|
{0, 0, 0, 0, 0, 0}
|
|
};
|
|
|
|
static param_export_t params[] = {
|
|
{"pretty_format", PARAM_INT, &jsonrpc_pretty_format},
|
|
{"fifo_name", PARAM_STRING, &jsonrpc_fifo},
|
|
{"fifo_mode", PARAM_INT, &jsonrpc_fifo_mode},
|
|
{"fifo_group", PARAM_STRING, &jsonrpc_fifo_gid_s},
|
|
{"fifo_group", PARAM_INT, &jsonrpc_fifo_gid},
|
|
{"fifo_user", PARAM_STRING, &jsonrpc_fifo_uid_s},
|
|
{"fifo_user", PARAM_INT, &jsonrpc_fifo_uid},
|
|
{"fifo_reply_dir", PARAM_STRING, &jsonrpc_fifo_reply_dir},
|
|
{"transport", PARAM_INT, &jsonrpc_transport},
|
|
{0, 0, 0}
|
|
};
|
|
|
|
static int jsonrpc_pv_get_jrpl(sip_msg_t *msg, pv_param_t *param, pv_value_t *res);
|
|
static int jsonrpc_pv_parse_jrpl_name(pv_spec_t *sp, str *in);
|
|
|
|
static pv_export_t mod_pvs[] = {
|
|
{ {"jsonrpl", sizeof("jsonrpl")-1}, PVT_OTHER, jsonrpc_pv_get_jrpl, 0,
|
|
jsonrpc_pv_parse_jrpl_name, 0, 0, 0 },
|
|
{ {0, 0}, 0, 0, 0, 0, 0, 0, 0 }
|
|
};
|
|
|
|
/** module exports */
|
|
struct module_exports exports= {
|
|
"jsonrpc-s",
|
|
DEFAULT_DLFLAGS, /* dlopen flags */
|
|
cmds,
|
|
params,
|
|
0, /* exported statistics */
|
|
0, /* exported MI functions */
|
|
mod_pvs, /* exported pseudo-variables */
|
|
0, /* extra processes */
|
|
mod_init, /* module initialization function */
|
|
0,
|
|
mod_destroy,/* module destroy function */
|
|
child_init /* per-child init function */
|
|
};
|
|
|
|
|
|
typedef struct jsonrpc_error {
|
|
int code;
|
|
str text;
|
|
} jsonrpc_error_t;
|
|
|
|
static jsonrpc_error_t _jsonrpc_error_table[] = {
|
|
{ -32700, { "Parse Error", 11 } },
|
|
{ -32600, { "Invalid Request", 15 } },
|
|
{ -32601, { "Method Not Found", 16 } },
|
|
{ -32602, { "Invalid Parameters", 18 } },
|
|
{ -32603, { "Internal Error", 14 } },
|
|
{ -32000, { "Execution Error", 15 } },
|
|
{0, { 0, 0 } }
|
|
};
|
|
|
|
typedef struct jsonrpc_plain_reply {
|
|
int rcode; /**< reply code */
|
|
str rtext; /**< reply reason text */
|
|
str rbody; /**< reply body */
|
|
} jsonrpc_plain_reply_t;
|
|
|
|
static jsonrpc_plain_reply_t _jsonrpc_plain_reply;
|
|
|
|
static void jsonrpc_set_plain_reply(int rcode, str *rtext, str *rbody,
|
|
void (*free_fn)(void*))
|
|
{
|
|
if(_jsonrpc_plain_reply.rbody.s) {
|
|
free_fn(_jsonrpc_plain_reply.rbody.s);
|
|
}
|
|
_jsonrpc_plain_reply.rcode = rcode;
|
|
_jsonrpc_plain_reply.rtext = *rtext;
|
|
if(rbody) {
|
|
_jsonrpc_plain_reply.rbody = *rbody;
|
|
} else {
|
|
_jsonrpc_plain_reply.rbody.s = NULL;
|
|
_jsonrpc_plain_reply.rbody.len = 0;
|
|
}
|
|
}
|
|
|
|
static void jsonrpc_reset_plain_reply(void (*free_fn)(void*))
|
|
{
|
|
if(_jsonrpc_plain_reply.rbody.s) {
|
|
free_fn(_jsonrpc_plain_reply.rbody.s);
|
|
}
|
|
memset(&_jsonrpc_plain_reply, 0, sizeof(jsonrpc_plain_reply_t));
|
|
}
|
|
|
|
/** 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 jsonrpc_fault(jsonrpc_ctx_t* ctx, int code, char* fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
ctx->http_code = code;
|
|
va_start(ap, fmt);
|
|
vsnprintf(jsonrpc_error_buf, JSONRPC_ERROR_REASON_BUF_LEN, fmt, ap);
|
|
va_end(ap);
|
|
ctx->http_text.len = strlen(jsonrpc_error_buf);
|
|
ctx->http_text.s = jsonrpc_error_buf;
|
|
if(ctx->error_code == 0) ctx->error_code = -32000;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
/** Initialize jsonrpc reply data structure.
|
|
*
|
|
* This function initializes the data structure that contains all data related
|
|
* to the jsonrpc reply being created. The function must be called before any
|
|
* other function that adds data to the reply.
|
|
* @param ctx jsonrpc_ctx_t structure to be initialized.
|
|
* @return 0 on success, a negative number on error.
|
|
*/
|
|
static int jsonrpc_init_reply(jsonrpc_ctx_t *ctx)
|
|
{
|
|
ctx->http_code = 200;
|
|
ctx->http_text = JSONRPC_REASON_OK;
|
|
ctx->jrpl = srjson_NewDoc(NULL);
|
|
if(ctx->jrpl==NULL) {
|
|
LM_ERR("Failed to init the reply json document\n");
|
|
return -1;
|
|
}
|
|
ctx->jrpl->root = srjson_CreateObject(ctx->jrpl);
|
|
if(ctx->jrpl->root==NULL) {
|
|
LM_ERR("Failed to init the reply json root node\n");
|
|
return -1;
|
|
}
|
|
srjson_AddStrStrToObject(ctx->jrpl, ctx->jrpl->root,
|
|
"jsonrpc", 7,
|
|
"2.0", 3);
|
|
|
|
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 jsonrpc request that
|
|
* generated the reply.
|
|
* @return 1 if the reply was already sent, 0 on success, a negative number on
|
|
* error
|
|
*/
|
|
static int jsonrpc_send(jsonrpc_ctx_t* ctx)
|
|
{
|
|
srjson_t *nj = NULL;
|
|
int i;
|
|
str rbuf;
|
|
|
|
if (ctx->reply_sent) return 1;
|
|
|
|
ctx->reply_sent = 1;
|
|
|
|
if(ctx->error_code != 0) {
|
|
/* fault handling */
|
|
nj = srjson_CreateObject(ctx->jrpl);
|
|
if(nj!=NULL) {
|
|
srjson_AddNumberToObject(ctx->jrpl, nj, "code",
|
|
ctx->error_code);
|
|
for(i=0; _jsonrpc_error_table[i].code!=0
|
|
&& _jsonrpc_error_table[i].code!=ctx->error_code; i++);
|
|
if(_jsonrpc_error_table[i].code!=0) {
|
|
srjson_AddStrStrToObject(ctx->jrpl, nj,
|
|
"message", 7,
|
|
_jsonrpc_error_table[i].text.s,
|
|
_jsonrpc_error_table[i].text.len);
|
|
} else {
|
|
srjson_AddStrStrToObject(ctx->jrpl, nj,
|
|
"message", 7, "Unexpected Error", 16);
|
|
}
|
|
srjson_AddItemToObject(ctx->jrpl, ctx->jrpl->root, "error", nj);
|
|
}
|
|
} else {
|
|
nj = srjson_GetObjectItem(ctx->jrpl, ctx->jrpl->root, "result");
|
|
if(nj==NULL) {
|
|
if (!ctx->rpl_node) {
|
|
if(ctx->flags & RET_ARRAY) {
|
|
ctx->rpl_node = srjson_CreateArray(ctx->jrpl);
|
|
} else {
|
|
ctx->rpl_node = srjson_CreateObject(ctx->jrpl);
|
|
}
|
|
if(ctx->rpl_node == 0) {
|
|
LM_ERR("failed to create the root array node\n");
|
|
}
|
|
}
|
|
srjson_AddItemToObject(ctx->jrpl, ctx->jrpl->root,
|
|
"result", ctx->rpl_node);
|
|
ctx->rpl_node = 0;
|
|
}
|
|
}
|
|
nj = srjson_GetObjectItem(ctx->jreq, ctx->jreq->root, "id");
|
|
if(nj!=NULL) {
|
|
if(nj->valuestring!=NULL) {
|
|
srjson_AddStrStrToObject(ctx->jrpl, ctx->jrpl->root,
|
|
"id", 2,
|
|
nj->valuestring, strlen(nj->valuestring));
|
|
} else {
|
|
srjson_AddNumberToObject(ctx->jrpl, ctx->jrpl->root, "id",
|
|
nj->valueint);
|
|
}
|
|
}
|
|
|
|
if(jsonrpc_pretty_format==0) {
|
|
rbuf.s = srjson_PrintUnformatted(ctx->jrpl, ctx->jrpl->root);
|
|
} else {
|
|
rbuf.s = srjson_Print(ctx->jrpl, ctx->jrpl->root);
|
|
}
|
|
if(rbuf.s!=NULL) {
|
|
rbuf.len = strlen(rbuf.s);
|
|
}
|
|
if (rbuf.s!=NULL) {
|
|
if(ctx->msg) {
|
|
xhttp_api.reply(ctx->msg, ctx->http_code, &ctx->http_text,
|
|
&JSONRPC_CONTENT_TYPE_HTML, &rbuf);
|
|
} else {
|
|
jsonrpc_set_plain_reply(ctx->http_code, &ctx->http_text, &rbuf,
|
|
ctx->jrpl->free_fn);
|
|
rbuf.s=NULL;
|
|
}
|
|
} else {
|
|
if(ctx->msg) {
|
|
xhttp_api.reply(ctx->msg, ctx->http_code, &ctx->http_text,
|
|
NULL, NULL);
|
|
} else {
|
|
jsonrpc_set_plain_reply(ctx->http_code, &ctx->http_text, NULL,
|
|
ctx->jrpl->free_fn);
|
|
}
|
|
}
|
|
if (rbuf.s!=NULL) {
|
|
ctx->jrpl->free_fn(rbuf.s);
|
|
}
|
|
|
|
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 srjson_t* jsonrpc_print_value(jsonrpc_ctx_t* ctx, char fmt, va_list* ap)
|
|
|
|
{
|
|
srjson_t *nj = NULL;
|
|
char buf[JSONRPC_PRINT_VALUE_BUF_LEN];
|
|
time_t dt;
|
|
struct tm* t;
|
|
str *sp;
|
|
|
|
switch(fmt) {
|
|
case 'd':
|
|
nj = srjson_CreateNumber(ctx->jrpl, va_arg(*ap, int));
|
|
break;
|
|
case 'u':
|
|
nj = srjson_CreateNumber(ctx->jrpl, va_arg(*ap, unsigned int));
|
|
break;
|
|
case 'f':
|
|
nj = srjson_CreateNumber(ctx->jrpl, va_arg(*ap, double));
|
|
break;
|
|
case 'b':
|
|
nj = srjson_CreateBool(ctx->jrpl, ((va_arg(*ap, int)==0)?0:1));
|
|
break;
|
|
case 't':
|
|
dt = va_arg(*ap, time_t);
|
|
t = gmtime(&dt);
|
|
if (strftime(buf, JSONRPC_PRINT_VALUE_BUF_LEN,
|
|
"%Y%m%dT%H:%M:%S", t) == 0) {
|
|
LM_ERR("Error while converting time\n");
|
|
return NULL;
|
|
}
|
|
nj = srjson_CreateString(ctx->jrpl, buf);
|
|
break;
|
|
case 's':
|
|
nj = srjson_CreateString(ctx->jrpl, va_arg(*ap, char*));
|
|
break;
|
|
case 'S':
|
|
sp = va_arg(*ap, str*);
|
|
nj = srjson_CreateStr(ctx->jrpl, sp->s, sp->len);
|
|
break;
|
|
default:
|
|
LM_ERR("Invalid formatting character [%c]\n", fmt);
|
|
return NULL;
|
|
}
|
|
return nj;
|
|
}
|
|
|
|
|
|
|
|
/** 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 jsonrpc reply being generated.
|
|
*/
|
|
static int jsonrpc_add(jsonrpc_ctx_t* ctx, char* fmt, ...)
|
|
{
|
|
srjson_t *nj = NULL;
|
|
void **void_ptr;
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
while(*fmt) {
|
|
if (*fmt == '{' || *fmt == '[') {
|
|
void_ptr = va_arg(ap, void**);
|
|
if (*fmt == '{') {
|
|
nj = srjson_CreateObject(ctx->jrpl);
|
|
} else {
|
|
nj = srjson_CreateArray(ctx->jrpl);
|
|
}
|
|
*void_ptr = nj;
|
|
} else {
|
|
nj = jsonrpc_print_value(ctx, *fmt, &ap);
|
|
}
|
|
|
|
if(nj==NULL) goto err;
|
|
if(ctx->flags & RET_ARRAY) {
|
|
if (ctx->rpl_node==NULL) {
|
|
ctx->rpl_node = srjson_CreateArray(ctx->jrpl);
|
|
if(ctx->rpl_node == 0) {
|
|
LM_ERR("failed to create the root array node\n");
|
|
goto err;
|
|
}
|
|
}
|
|
srjson_AddItemToArray(ctx->jrpl, ctx->rpl_node, nj);
|
|
} else {
|
|
if (ctx->rpl_node) srjson_Delete(ctx->jrpl, ctx->rpl_node);
|
|
ctx->rpl_node = nj;
|
|
}
|
|
|
|
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 jsonrpc
|
|
* request. This function will extract the current parameter from the jsonrpc
|
|
* URL and attempts to convert it to the type requested by the management
|
|
* function that called it.
|
|
*/
|
|
static int jsonrpc_scan(jsonrpc_ctx_t* ctx, char* fmt, ...)
|
|
{
|
|
int *int_ptr;
|
|
unsigned int *uint_ptr;
|
|
char **char_ptr;
|
|
double *double_ptr;
|
|
str *str_ptr;
|
|
int mandatory_param = 1;
|
|
int modifiers = 0;
|
|
int auto_convert = 0;
|
|
char* orig_fmt;
|
|
va_list ap;
|
|
str stmp;
|
|
|
|
if(ctx->req_node==NULL)
|
|
return 0;
|
|
|
|
orig_fmt=fmt;
|
|
va_start(ap, fmt);
|
|
while(*fmt && ctx->req_node) {
|
|
switch(*fmt) {
|
|
case '*': /* start of optional parameters */
|
|
mandatory_param = 0;
|
|
modifiers++;
|
|
fmt++;
|
|
continue;
|
|
case '.': /* autoconvert */
|
|
modifiers++;
|
|
fmt++;
|
|
auto_convert = 1;
|
|
continue;
|
|
case 'b': /* Bool */
|
|
case 't': /* Date and time */
|
|
case 'd': /* Integer */
|
|
int_ptr = va_arg(ap, int*);
|
|
*int_ptr = ctx->req_node->valueint;
|
|
break;
|
|
case 'u': /* Integer */
|
|
uint_ptr = va_arg(ap, unsigned int*);
|
|
*uint_ptr = (unsigned int)ctx->req_node->valueint;
|
|
break;
|
|
case 'f': /* double */
|
|
double_ptr = va_arg(ap, double*);
|
|
*double_ptr = ctx->req_node->valuedouble;
|
|
break;
|
|
case 's': /* zero terminated string */
|
|
char_ptr = va_arg(ap, char**);
|
|
if(ctx->req_node->type==srjson_String) {
|
|
*char_ptr = ctx->req_node->valuestring;
|
|
} else if(auto_convert == 1) {
|
|
if(ctx->req_node->type==srjson_Number) {
|
|
*char_ptr = int2str(ctx->req_node->valueint, &stmp.len);
|
|
} else {
|
|
*char_ptr = NULL;
|
|
goto error;
|
|
}
|
|
} else {
|
|
*char_ptr = NULL;
|
|
goto error;
|
|
}
|
|
break;
|
|
case 'S': /* str structure */
|
|
str_ptr = va_arg(ap, str*);
|
|
if(ctx->req_node->type==srjson_String) {
|
|
str_ptr->s = ctx->req_node->valuestring;
|
|
str_ptr->len = strlen(ctx->req_node->valuestring);
|
|
} else if(auto_convert == 1) {
|
|
if(ctx->req_node->type==srjson_Number) {
|
|
str_ptr->s = int2str(ctx->req_node->valueint,
|
|
&str_ptr->len);
|
|
} else {
|
|
str_ptr->s = NULL;
|
|
str_ptr->len = 0;
|
|
goto error;
|
|
}
|
|
} else {
|
|
str_ptr->s = NULL;
|
|
str_ptr->len = 0;
|
|
goto error;
|
|
}
|
|
break;
|
|
case '{':
|
|
case '[':
|
|
LM_ERR("Unsupported param type '%c'\n", *fmt);
|
|
jsonrpc_fault(ctx, 400, "Unsupported param type");
|
|
goto error;
|
|
default:
|
|
LM_ERR("Invalid param type in formatting string: [%c]\n", *fmt);
|
|
jsonrpc_fault(ctx, 500,
|
|
"Internal Server Error (inval formatting str)");
|
|
goto error;
|
|
}
|
|
fmt++;
|
|
auto_convert = 0;
|
|
ctx->req_node = ctx->req_node->next;
|
|
}
|
|
/* error if there is still a scan char type and it is not optional */
|
|
if(*fmt && mandatory_param==1)
|
|
goto error;
|
|
|
|
va_end(ap);
|
|
return (int)(fmt-orig_fmt)-modifiers;
|
|
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 jsonrpc reply being constructed.
|
|
*/
|
|
static int jsonrpc_rpl_printf(jsonrpc_ctx_t* ctx, char* fmt, ...)
|
|
{
|
|
int n, buf_size;
|
|
char *buf = 0;
|
|
char tbuf[JSONRPC_PRINT_VALUE_BUF_LEN];
|
|
va_list ap;
|
|
srjson_t *nj = NULL;
|
|
|
|
buf = tbuf;
|
|
buf_size = JSONRPC_PRINT_VALUE_BUF_LEN;
|
|
while (1) {
|
|
/* try to print in the allocated space. */
|
|
va_start(ap, fmt);
|
|
n = vsnprintf(buf, buf_size, fmt, ap);
|
|
va_end(ap);
|
|
/* if that worked, return the string. */
|
|
if (n > -1 && n < buf_size) {
|
|
nj = srjson_CreateString(ctx->jrpl, buf);
|
|
if(nj==NULL) {
|
|
LM_ERR("failed to create the value node\n");
|
|
if(buf && buf!=tbuf) jsonrpc_free(buf);
|
|
return -1;
|
|
}
|
|
if(ctx->flags & RET_ARRAY) {
|
|
if (ctx->rpl_node==NULL) {
|
|
ctx->rpl_node = srjson_CreateArray(ctx->jrpl);
|
|
if(ctx->rpl_node == 0) {
|
|
LM_ERR("failed to create the root array node\n");
|
|
if(buf && buf!=tbuf) jsonrpc_free(buf);
|
|
return -1;
|
|
}
|
|
}
|
|
srjson_AddItemToArray(ctx->jrpl, ctx->rpl_node, nj);
|
|
} else {
|
|
if (ctx->rpl_node) srjson_Delete(ctx->jrpl, ctx->rpl_node);
|
|
ctx->rpl_node = nj;
|
|
}
|
|
if(buf && buf!=tbuf) jsonrpc_free(buf);
|
|
return 0;
|
|
}
|
|
/* else try again with more space. */
|
|
if (n > -1) { /* glibc 2.1 */
|
|
buf_size = n + 1; /* precisely what is needed */
|
|
} else { /* glibc 2.0 */
|
|
buf_size *= 2; /* twice the old size */
|
|
}
|
|
if(buf && buf!=tbuf) jsonrpc_free(buf);
|
|
if ((buf = jsonrpc_malloc(buf_size)) == 0) {
|
|
jsonrpc_fault(ctx, 500, "Internal Server Error (No memory left)");
|
|
LM_ERR("no memory left for rpc printf\n");
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/** Adds a new member to structure.
|
|
*/
|
|
static int jsonrpc_struct_add(srjson_t *jnode, char* fmt, ...)
|
|
{
|
|
srjson_t *nj = NULL;
|
|
srjson_t *wj = NULL;
|
|
jsonrpc_ctx_t* ctx;
|
|
va_list ap;
|
|
void **void_ptr;
|
|
str mname;
|
|
int isobject;
|
|
|
|
if(jnode==NULL) {
|
|
LM_ERR("invalid json node parameter\n");
|
|
return -1;
|
|
}
|
|
if(jnode->type!=srjson_Object && jnode->type!=srjson_Array) {
|
|
LM_ERR("json node parameter is not object or array (%d)\n",
|
|
jnode->type);
|
|
return -1;
|
|
}
|
|
isobject = (jnode->type==srjson_Object);
|
|
|
|
ctx = &_jsonrpc_ctx;
|
|
if(ctx->jrpl==NULL) {
|
|
LM_ERR("reply object not initialized in rpl context\n");
|
|
return -1;
|
|
}
|
|
|
|
va_start(ap, fmt);
|
|
while(*fmt) {
|
|
mname.s = va_arg(ap, char*);
|
|
mname.len = (mname.s?strlen(mname.s):0);
|
|
|
|
if (*fmt == '{' || *fmt == '[') {
|
|
void_ptr = va_arg(ap, void**);
|
|
if (*fmt == '{') {
|
|
nj = srjson_CreateObject(ctx->jrpl);
|
|
} else {
|
|
nj = srjson_CreateArray(ctx->jrpl);
|
|
}
|
|
*void_ptr = nj;
|
|
} else {
|
|
nj = jsonrpc_print_value(ctx, *fmt, &ap);
|
|
}
|
|
|
|
if(nj==NULL) goto err;
|
|
if(isobject) {
|
|
/* add as member to object */
|
|
srjson_AddItemToObject(ctx->jrpl, jnode, mname.s, nj);
|
|
} else {
|
|
/* wrap member in a new object and add to array */
|
|
wj = srjson_CreateObject(ctx->jrpl);
|
|
if(wj==NULL) {
|
|
srjson_Delete(ctx->jrpl, nj);
|
|
goto err;
|
|
}
|
|
srjson_AddItemToObject(ctx->jrpl, wj, mname.s, nj);
|
|
srjson_AddItemToArray(ctx->jrpl, jnode, wj);
|
|
}
|
|
fmt++;
|
|
}
|
|
va_end(ap);
|
|
return 0;
|
|
err:
|
|
va_end(ap);
|
|
return -1;
|
|
}
|
|
|
|
|
|
/** Adds a new member to structure.
|
|
*/
|
|
static int jsonrpc_array_add(srjson_t *jnode, char* fmt, ...)
|
|
{
|
|
srjson_t *nj = NULL;
|
|
jsonrpc_ctx_t* ctx;
|
|
va_list ap;
|
|
void **void_ptr;
|
|
|
|
if(jnode==NULL) {
|
|
LM_ERR("invalid json node parameter\n");
|
|
return -1;
|
|
}
|
|
if(jnode->type!=srjson_Array) {
|
|
LM_ERR("json node parameter is not array (%d)\n", jnode->type);
|
|
return -1;
|
|
}
|
|
|
|
ctx = &_jsonrpc_ctx;
|
|
if(ctx->jrpl==NULL) {
|
|
LM_ERR("reply object not initialized in rpl context\n");
|
|
return -1;
|
|
}
|
|
|
|
va_start(ap, fmt);
|
|
while(*fmt) {
|
|
if (*fmt == '{' || *fmt == '[') {
|
|
void_ptr = va_arg(ap, void**);
|
|
if (*fmt == '{') {
|
|
nj = srjson_CreateObject(ctx->jrpl);
|
|
} else {
|
|
nj = srjson_CreateArray(ctx->jrpl);
|
|
}
|
|
*void_ptr = nj;
|
|
} else {
|
|
nj = jsonrpc_print_value(ctx, *fmt, &ap);
|
|
}
|
|
|
|
if(nj==NULL) goto err;
|
|
srjson_AddItemToArray(ctx->jrpl, jnode, nj);
|
|
fmt++;
|
|
}
|
|
va_end(ap);
|
|
return 0;
|
|
err:
|
|
va_end(ap);
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int jsonrpc_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 jsonrpc_struct_printf(srjson_t *jnode, char* mname, char* fmt, ...)
|
|
{
|
|
jsonrpc_ctx_t* ctx;
|
|
int n, buf_size;
|
|
char *buf = 0;
|
|
char tbuf[JSONRPC_PRINT_VALUE_BUF_LEN];
|
|
va_list ap;
|
|
srjson_t *nj = NULL;
|
|
|
|
if(jnode==NULL || mname==NULL) {
|
|
LM_ERR("invalid json node or member name parameter (%p/%p)\n",
|
|
jnode, mname);
|
|
return -1;
|
|
}
|
|
if(jnode->type!=srjson_Object) {
|
|
LM_ERR("json node parameter is not object (%d)\n", jnode->type);
|
|
return -1;
|
|
}
|
|
|
|
ctx = &_jsonrpc_ctx;
|
|
if(ctx->jrpl==NULL) {
|
|
LM_ERR("reply object not initialized in rpl context\n");
|
|
return -1;
|
|
}
|
|
|
|
buf = tbuf;
|
|
buf_size = JSONRPC_PRINT_VALUE_BUF_LEN;
|
|
while (1) {
|
|
/* try to print in the allocated space. */
|
|
va_start(ap, fmt);
|
|
n = vsnprintf(buf, buf_size, fmt, ap);
|
|
va_end(ap);
|
|
/* if that worked, return the string. */
|
|
if (n > -1 && n < buf_size) {
|
|
nj = srjson_CreateString(ctx->jrpl, buf);
|
|
if(nj==NULL) {
|
|
LM_ERR("failed to create the value node\n");
|
|
if(buf && buf!=tbuf) jsonrpc_free(buf);
|
|
return -1;
|
|
}
|
|
srjson_AddItemToObject(ctx->jrpl, jnode, mname, nj);
|
|
if(buf && buf!=tbuf) jsonrpc_free(buf);
|
|
return 0;
|
|
}
|
|
/* else try again with more space. */
|
|
if (n > -1) { /* glibc 2.1 */
|
|
buf_size = n + 1; /* precisely what is needed */
|
|
} else { /* glibc 2.0 */
|
|
buf_size *= 2; /* twice the old size */
|
|
}
|
|
if(buf && buf!=tbuf) jsonrpc_free(buf);
|
|
if ((buf = jsonrpc_malloc(buf_size)) == 0) {
|
|
jsonrpc_fault(ctx, 500, "Internal Server Error (No memory left)");
|
|
LM_ERR("no memory left for rpc printf\n");
|
|
return -1;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
/** Returns the RPC capabilities supported by the xmlrpc driver.
|
|
*/
|
|
static rpc_capabilities_t jsonrpc_capabilities(jsonrpc_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* jsonrpc_delayed_ctx_new(jsonrpc_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 jsonrpc_delayed_ctx_close(struct rpc_delayed_ctx* dctx)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
static void jsonrpc_clean_context(jsonrpc_ctx_t* ctx)
|
|
{
|
|
if (!ctx) return;
|
|
srjson_DeleteDoc(ctx->jreq);
|
|
if(ctx->rpl_node!=NULL) {
|
|
srjson_Delete(ctx->jrpl, ctx->rpl_node);
|
|
ctx->rpl_node = NULL;
|
|
}
|
|
srjson_DeleteDoc(ctx->jrpl);
|
|
}
|
|
|
|
static int mod_init(void)
|
|
{
|
|
int sep;
|
|
int len;
|
|
char *p;
|
|
|
|
/* bind the XHTTP API */
|
|
if(jsonrpc_transport==0 || jsonrpc_transport==1) {
|
|
if (xhttp_load_api(&xhttp_api) < 0) {
|
|
if(jsonrpc_transport==1) {
|
|
LM_ERR("cannot bind to XHTTP API\n");
|
|
return -1;
|
|
} else {
|
|
memset(&xhttp_api, 0, sizeof(xhttp_api_t));
|
|
}
|
|
}
|
|
}
|
|
if(jsonrpc_transport==0 || jsonrpc_transport==2) {
|
|
if(jsonrpc_fifo != NULL && *jsonrpc_fifo!=0) {
|
|
if(*jsonrpc_fifo != '/') {
|
|
if(runtime_dir!=NULL && *runtime_dir!=0) {
|
|
len = strlen(runtime_dir);
|
|
sep = 0;
|
|
if(runtime_dir[len-1]!='/') {
|
|
sep = 1;
|
|
}
|
|
len += sep + strlen(jsonrpc_fifo);
|
|
p = pkg_malloc(len + 1);
|
|
if(p==NULL) {
|
|
LM_ERR("no more pkg\n");
|
|
return -1;
|
|
}
|
|
strcpy(p, runtime_dir);
|
|
if(sep) strcat(p, "/");
|
|
strcat(p, jsonrpc_fifo);
|
|
jsonrpc_fifo = p;
|
|
LM_DBG("fifo path is [%s]\n", jsonrpc_fifo);
|
|
}
|
|
}
|
|
}
|
|
if(jsonrpc_init_fifo_file()<0) {
|
|
if(jsonrpc_transport==2) {
|
|
LM_ERR("cannot initialize fifo transport\n");
|
|
return -1;
|
|
} else {
|
|
jsonrpc_fifo = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
memset(&func_param, 0, sizeof(func_param));
|
|
func_param.send = (rpc_send_f)jsonrpc_send;
|
|
func_param.fault = (rpc_fault_f)jsonrpc_fault;
|
|
func_param.add = (rpc_add_f)jsonrpc_add;
|
|
func_param.scan = (rpc_scan_f)jsonrpc_scan;
|
|
func_param.rpl_printf = (rpc_rpl_printf_f)jsonrpc_rpl_printf;
|
|
func_param.struct_add = (rpc_struct_add_f)jsonrpc_struct_add;
|
|
func_param.array_add = (rpc_struct_add_f)jsonrpc_array_add;
|
|
func_param.struct_scan = (rpc_struct_scan_f)jsonrpc_struct_scan;
|
|
func_param.struct_printf = (rpc_struct_printf_f)jsonrpc_struct_printf;
|
|
func_param.capabilities = (rpc_capabilities_f)jsonrpc_capabilities;
|
|
func_param.delayed_ctx_new = (rpc_delayed_ctx_new_f)jsonrpc_delayed_ctx_new;
|
|
func_param.delayed_ctx_close =
|
|
(rpc_delayed_ctx_close_f)jsonrpc_delayed_ctx_close;
|
|
|
|
jsonrpc_register_rpc();
|
|
|
|
memset(&_jsonrpc_plain_reply, 0, sizeof(jsonrpc_plain_reply_t));
|
|
return 0;
|
|
}
|
|
|
|
static int child_init(int rank)
|
|
{
|
|
int pid;
|
|
|
|
if (rank==PROC_MAIN) {
|
|
if(jsonrpc_fifo != NULL) {
|
|
pid=fork_process(PROC_NOCHLDINIT, "JSONRPC-S FIFO", 1);
|
|
if (pid<0)
|
|
return -1; /* error */
|
|
if(pid==0) {
|
|
/* child */
|
|
|
|
/* initialize the config framework */
|
|
if (cfg_child_init())
|
|
return -1;
|
|
|
|
jsonrpc_fifo_process(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(rank==PROC_MAIN || rank==PROC_TCP_MAIN)
|
|
{
|
|
return 0; /* do nothing for the main process */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mod_destroy(void)
|
|
{
|
|
int n;
|
|
struct stat filestat;
|
|
|
|
if(jsonrpc_fifo==NULL)
|
|
return;
|
|
|
|
/* destroying the fifo file */
|
|
n=stat(jsonrpc_fifo, &filestat);
|
|
if (n==0){
|
|
/* FIFO exist, delete it (safer) if not config check */
|
|
if(config_check==0) {
|
|
if (unlink(jsonrpc_fifo)<0){
|
|
LM_ERR("cannot delete the fifo (%s): %s\n",
|
|
jsonrpc_fifo, strerror(errno));
|
|
goto error;
|
|
}
|
|
}
|
|
} else if (n<0 && errno!=ENOENT) {
|
|
LM_ERR("FIFO stat failed: %s\n", strerror(errno));
|
|
goto error;
|
|
}
|
|
|
|
return;
|
|
error:
|
|
return;
|
|
}
|
|
|
|
static int jsonrpc_dispatch(sip_msg_t* msg, char* s1, char* s2)
|
|
{
|
|
rpc_export_t* rpce;
|
|
jsonrpc_ctx_t* ctx;
|
|
int ret = 0;
|
|
srjson_t *nj = NULL;
|
|
str val;
|
|
|
|
if(!IS_HTTP(msg)) {
|
|
LM_DBG("Got non HTTP msg\n");
|
|
return NONSIP_MSG_PASS;
|
|
}
|
|
|
|
/* initialize jsonrpc context */
|
|
ctx = &_jsonrpc_ctx;
|
|
memset(ctx, 0, sizeof(jsonrpc_ctx_t));
|
|
ctx->msg = msg;
|
|
/* parse the jsonrpc request */
|
|
ctx->jreq = srjson_NewDoc(NULL);
|
|
if(ctx->jreq==NULL) {
|
|
LM_ERR("Failed to init the json document\n");
|
|
return NONSIP_MSG_ERROR;
|
|
}
|
|
|
|
ctx->jreq->buf.s = get_body(msg);
|
|
ctx->jreq->buf.len = strlen(ctx->jreq->buf.s);
|
|
ctx->jreq->root = srjson_Parse(ctx->jreq, ctx->jreq->buf.s);
|
|
if(ctx->jreq->root == NULL)
|
|
{
|
|
LM_ERR("invalid json doc [[%s]]\n", ctx->jreq->buf.s);
|
|
return NONSIP_MSG_ERROR;
|
|
}
|
|
if (jsonrpc_init_reply(ctx) < 0) goto send_reply;
|
|
|
|
/* sanity checks on jsonrpc request */
|
|
nj = srjson_GetObjectItem(ctx->jreq, ctx->jreq->root, "jsonrpc");
|
|
if(nj==NULL || nj->valuestring==NULL) {
|
|
LM_ERR("missing or invalid jsonrpc field in request\n");
|
|
goto send_reply;
|
|
}
|
|
val.s = nj->valuestring;
|
|
val.len = strlen(val.s);
|
|
if(val.len!=3 || strncmp(val.s, "2.0", 3)!=0) {
|
|
LM_ERR("unsupported jsonrpc version [%.*s]\n", val.len, val.s);
|
|
goto send_reply;
|
|
}
|
|
/* run jsonrpc command */
|
|
nj = srjson_GetObjectItem(ctx->jreq, ctx->jreq->root, "method");
|
|
if(nj==NULL || nj->valuestring==NULL) {
|
|
LM_ERR("missing or invalid jsonrpc method field in request\n");
|
|
goto send_reply;
|
|
}
|
|
val.s = nj->valuestring;
|
|
val.len = strlen(val.s);
|
|
ctx->method = val.s;
|
|
rpce = find_rpc_export(ctx->method, 0);
|
|
if (!rpce || !rpce->function) {
|
|
LM_ERR("method callback not found [%.*s]\n", val.len, val.s);
|
|
jsonrpc_fault(ctx, 500, "Method Not Found");
|
|
goto send_reply;
|
|
}
|
|
ctx->flags = rpce->flags;
|
|
nj = srjson_GetObjectItem(ctx->jreq, ctx->jreq->root, "params");
|
|
if(nj!=NULL && nj->type!=srjson_Array && nj->type!=srjson_Object) {
|
|
LM_ERR("params field is not an array or object\n");
|
|
goto send_reply;
|
|
}
|
|
if(nj!=NULL) ctx->req_node = nj->child;
|
|
rpce->function(&func_param, ctx);
|
|
|
|
send_reply:
|
|
if (!ctx->reply_sent) {
|
|
ret = jsonrpc_send(ctx);
|
|
}
|
|
jsonrpc_clean_context(ctx);
|
|
if (ret < 0) return -1;
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int jsonrpc_exec_ex(str *cmd, str *rpath)
|
|
{
|
|
rpc_export_t* rpce;
|
|
jsonrpc_ctx_t* ctx;
|
|
int ret;
|
|
srjson_t *nj = NULL;
|
|
str val;
|
|
str scmd;
|
|
|
|
scmd = *cmd;
|
|
|
|
/* initialize jsonrpc context */
|
|
ctx = &_jsonrpc_ctx;
|
|
memset(ctx, 0, sizeof(jsonrpc_ctx_t));
|
|
ctx->msg = NULL; /* mark it not send a reply out */
|
|
/* parse the jsonrpc request */
|
|
ctx->jreq = srjson_NewDoc(NULL);
|
|
if(ctx->jreq==NULL) {
|
|
LM_ERR("Failed to init the json document\n");
|
|
return -1;
|
|
}
|
|
ctx->jreq->buf = scmd;
|
|
ctx->jreq->root = srjson_Parse(ctx->jreq, ctx->jreq->buf.s);
|
|
if(ctx->jreq->root == NULL)
|
|
{
|
|
LM_ERR("invalid json doc [[%.*s]]\n",
|
|
ctx->jreq->buf.len, ctx->jreq->buf.s);
|
|
return -1;
|
|
}
|
|
ret = -1;
|
|
if (jsonrpc_init_reply(ctx) < 0) goto send_reply;
|
|
jsonrpc_reset_plain_reply(ctx->jrpl->free_fn);
|
|
|
|
|
|
/* sanity checks on jsonrpc request */
|
|
nj = srjson_GetObjectItem(ctx->jreq, ctx->jreq->root, "jsonrpc");
|
|
if(nj==NULL) {
|
|
LM_ERR("missing jsonrpc field in request\n");
|
|
goto send_reply;
|
|
}
|
|
val.s = nj->valuestring;
|
|
val.len = strlen(val.s);
|
|
if(val.len!=3 || strncmp(val.s, "2.0", 3)!=0) {
|
|
LM_ERR("unsupported jsonrpc version [%.*s]\n", val.len, val.s);
|
|
goto send_reply;
|
|
}
|
|
/* reply name */
|
|
if(rpath!=NULL) {
|
|
if(rpath->s==NULL || rpath->len<=0) {
|
|
LM_ERR("empty buffer to store the reply name\n");
|
|
goto send_reply;
|
|
}
|
|
nj = srjson_GetObjectItem(ctx->jreq, ctx->jreq->root, "reply_name");
|
|
if(nj==NULL) {
|
|
LM_ERR("missing reply_name field in request\n");
|
|
goto send_reply;
|
|
}
|
|
val.s = nj->valuestring;
|
|
val.len = strlen(val.s);
|
|
if(val.len>=rpath->len) {
|
|
LM_ERR("no space to store reply_name field\n");
|
|
goto send_reply;
|
|
}
|
|
strncpy(rpath->s, val.s, val.len);
|
|
rpath->s[val.len] = 0;
|
|
rpath->len = val.len;
|
|
}
|
|
/* run jsonrpc command */
|
|
nj = srjson_GetObjectItem(ctx->jreq, ctx->jreq->root, "method");
|
|
if(nj==NULL) {
|
|
LM_ERR("missing jsonrpc method field in request\n");
|
|
goto send_reply;
|
|
}
|
|
val.s = nj->valuestring;
|
|
val.len = strlen(val.s);
|
|
ctx->method = val.s;
|
|
rpce = find_rpc_export(ctx->method, 0);
|
|
if (!rpce || !rpce->function) {
|
|
LM_ERR("method callback not found [%.*s]\n", val.len, val.s);
|
|
jsonrpc_fault(ctx, 500, "Method Not Found");
|
|
goto send_reply;
|
|
}
|
|
ctx->flags = rpce->flags;
|
|
nj = srjson_GetObjectItem(ctx->jreq, ctx->jreq->root, "params");
|
|
if(nj!=NULL && nj->type!=srjson_Array && nj->type!=srjson_Object) {
|
|
LM_ERR("params field is not an array or object\n");
|
|
goto send_reply;
|
|
}
|
|
if(nj!=NULL) ctx->req_node = nj->child;
|
|
rpce->function(&func_param, ctx);
|
|
ret = 1;
|
|
|
|
send_reply:
|
|
if (!ctx->reply_sent) {
|
|
ret = jsonrpc_send(ctx);
|
|
}
|
|
jsonrpc_clean_context(ctx);
|
|
if (ret < 0) return -1;
|
|
return 1;
|
|
}
|
|
|
|
static int jsonrpc_exec(sip_msg_t* msg, char* cmd, char* s2)
|
|
{
|
|
str scmd;
|
|
|
|
if(fixup_get_svalue(msg, (gparam_t*)cmd, &scmd)<0 || scmd.len<=0) {
|
|
LM_ERR("cannot get the rpc command parameter\n");
|
|
return -1;
|
|
}
|
|
return jsonrpc_exec_ex(&scmd, NULL);
|
|
}
|
|
/**
|
|
*
|
|
*/
|
|
static const char* jsonrpc_rpc_echo_doc[2] = {
|
|
"Sample echo command",
|
|
0
|
|
};
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void jsonrpc_rpc_echo(rpc_t* rpc, void* ctx)
|
|
{
|
|
str sval;
|
|
int ival = 0;
|
|
int ret;
|
|
ret = rpc->scan(ctx, "S*d", &sval, &ival);
|
|
if(ret>0) {
|
|
LM_DBG("READ STR: %.*s\n", sval.len, sval.s);
|
|
rpc->add(ctx, "S", &sval);
|
|
}
|
|
if(ret>1) {
|
|
LM_DBG("READ INT: %d\n", ival);
|
|
rpc->add(ctx, "d", ival);
|
|
}
|
|
}
|
|
/**
|
|
*
|
|
*/
|
|
static rpc_export_t jsonrpc_rpc[] = {
|
|
{"jsonrpc.echo", jsonrpc_rpc_echo, jsonrpc_rpc_echo_doc, RET_ARRAY},
|
|
{0, 0, 0, 0}
|
|
};
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static int jsonrpc_register_rpc(void)
|
|
{
|
|
if (rpc_register_array(jsonrpc_rpc)!=0)
|
|
{
|
|
LM_ERR("failed to register RPC commands\n");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static int jsonrpc_pv_get_jrpl(sip_msg_t *msg, pv_param_t *param, pv_value_t *res)
|
|
{
|
|
switch(param->pvn.u.isname.name.n)
|
|
{
|
|
case 0:
|
|
return pv_get_uintval(msg, param, res,
|
|
(unsigned int)_jsonrpc_plain_reply.rcode);
|
|
case 1:
|
|
if(_jsonrpc_plain_reply.rtext.s==NULL)
|
|
return pv_get_null(msg, param, res);
|
|
return pv_get_strval(msg, param, res, &_jsonrpc_plain_reply.rtext);
|
|
case 2:
|
|
if(_jsonrpc_plain_reply.rbody.s==NULL)
|
|
return pv_get_null(msg, param, res);
|
|
return pv_get_strval(msg, param, res, &_jsonrpc_plain_reply.rbody);
|
|
default:
|
|
return pv_get_null(msg, param, res);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static int jsonrpc_pv_parse_jrpl_name(pv_spec_t *sp, str *in)
|
|
{
|
|
if(in->len!=4) {
|
|
LM_ERR("unknown inner name [%.*s]\n", in->len, in->s);
|
|
return -1;
|
|
}
|
|
if(strncmp(in->s, "code", 4)==0) {
|
|
sp->pvp.pvn.u.isname.name.n = 0;
|
|
} else if(strncmp(in->s, "text", 4)==0) {
|
|
sp->pvp.pvn.u.isname.name.n = 1;
|
|
} else if(strncmp(in->s, "body", 4)==0) {
|
|
sp->pvp.pvn.u.isname.name.n = 2;
|
|
} else {
|
|
LM_ERR("unknown inner name [%.*s]\n", in->len, in->s);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* FIFO TRANSPORT */
|
|
|
|
/*! \brief Initialize fifo transport */
|
|
static int jsonrpc_init_fifo_file(void)
|
|
{
|
|
int n;
|
|
struct stat filestat;
|
|
|
|
/* checking the jsonrpc_fifo module param */
|
|
if (jsonrpc_fifo==NULL || *jsonrpc_fifo == 0) {
|
|
jsonrpc_fifo=NULL;
|
|
LM_DBG("No fifo configured\n");
|
|
return 0;
|
|
}
|
|
|
|
LM_DBG("testing if fifo file exists ...\n");
|
|
n=stat(jsonrpc_fifo, &filestat);
|
|
if (n==0) {
|
|
/* FIFO exist, delete it (safer) if no config check */
|
|
if(config_check==0) {
|
|
if (unlink(jsonrpc_fifo)<0){
|
|
LM_ERR("Cannot delete old fifo (%s): %s\n",
|
|
jsonrpc_fifo, strerror(errno));
|
|
return -1;
|
|
}
|
|
}
|
|
} else if (n<0 && errno!=ENOENT){
|
|
LM_ERR("MI FIFO stat failed: %s\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
/* checking the fifo_reply_dir param */
|
|
if(!jsonrpc_fifo_reply_dir || *jsonrpc_fifo_reply_dir == 0) {
|
|
LM_ERR("fifo_reply_dir parameter is empty\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Check if the directory for the reply fifo exists */
|
|
n = stat(jsonrpc_fifo_reply_dir, &filestat);
|
|
if(n < 0){
|
|
LM_ERR("Directory stat for MI Fifo reply failed: %s\n", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if(S_ISDIR(filestat.st_mode) == 0){
|
|
LM_ERR("fifo_reply_dir parameter is not a directory\n");
|
|
return -1;
|
|
}
|
|
|
|
/* check fifo_mode */
|
|
if(!jsonrpc_fifo_mode){
|
|
LM_WARN("cannot specify fifo_mode = 0, forcing it to rw-------\n");
|
|
jsonrpc_fifo_mode = S_IRUSR | S_IWUSR;
|
|
}
|
|
|
|
if (jsonrpc_fifo_uid_s){
|
|
if (user2uid(&jsonrpc_fifo_uid, &jsonrpc_fifo_gid, jsonrpc_fifo_uid_s)<0){
|
|
LM_ERR("Bad user name %s\n", jsonrpc_fifo_uid_s);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (jsonrpc_fifo_gid_s){
|
|
if (group2gid(&jsonrpc_fifo_gid, jsonrpc_fifo_gid_s)<0){
|
|
LM_ERR("Bad group name %s\n", jsonrpc_fifo_gid_s);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* add space for one extra process */
|
|
register_procs(1);
|
|
|
|
/* add child to update local config framework structures */
|
|
cfg_register_child(1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int jsonrpc_fifo_read = 0;
|
|
static int jsonrpc_fifo_write = 0;
|
|
#define JSONRPC_MAX_FILENAME 128
|
|
static char *jsonrpc_reply_fifo_s = NULL;
|
|
static int jsonrpc_reply_fifo_len = 0;
|
|
|
|
/*! \brief Initialize Fifo server */
|
|
FILE *jsonrpc_init_fifo_server(char *fifo_name, int fifo_mode,
|
|
int fifo_uid, int fifo_gid, char* fifo_reply_dir)
|
|
{
|
|
FILE *fifo_stream;
|
|
long opt;
|
|
|
|
/* create FIFO ... */
|
|
if ((mkfifo(fifo_name, fifo_mode)<0)) {
|
|
LM_ERR("Can't create FIFO: %s (mode=%d)\n", strerror(errno), fifo_mode);
|
|
return 0;
|
|
}
|
|
|
|
LM_DBG("FIFO created @ %s\n", fifo_name );
|
|
|
|
if ((chmod(fifo_name, fifo_mode)<0)) {
|
|
LM_ERR("Can't chmod FIFO: %s (mode=%d)\n", strerror(errno), fifo_mode);
|
|
return 0;
|
|
}
|
|
|
|
if ((fifo_uid!=-1) || (fifo_gid!=-1)){
|
|
if (chown(fifo_name, fifo_uid, fifo_gid)<0){
|
|
LM_ERR("Failed to change the owner/group for %s to %d.%d; %s[%d]\n",
|
|
fifo_name, fifo_uid, fifo_gid, strerror(errno), errno);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
LM_DBG("fifo %s opened, mode=%o\n", fifo_name, fifo_mode );
|
|
|
|
/* open it non-blocking or else wait here until someone
|
|
* opens it for writing */
|
|
jsonrpc_fifo_read=open(fifo_name, O_RDONLY|O_NONBLOCK, 0);
|
|
if (jsonrpc_fifo_read<0) {
|
|
LM_ERR("Can't open fifo %s for reading - fifo_read did not open: %s\n", fifo_name, strerror(errno));
|
|
return 0;
|
|
}
|
|
|
|
fifo_stream = fdopen(jsonrpc_fifo_read, "r");
|
|
if (fifo_stream==NULL) {
|
|
LM_ERR("fdopen failed on %s: %s\n", fifo_name, strerror(errno));
|
|
return 0;
|
|
}
|
|
|
|
/* make sure the read fifo will not close */
|
|
jsonrpc_fifo_write=open(fifo_name, O_WRONLY|O_NONBLOCK, 0);
|
|
if (jsonrpc_fifo_write<0) {
|
|
LM_ERR("fifo_write did not open: %s\n", strerror(errno));
|
|
return 0;
|
|
}
|
|
/* set read fifo blocking mode */
|
|
if ((opt=fcntl(jsonrpc_fifo_read, F_GETFL))==-1){
|
|
LM_ERR("fcntl(F_GETFL) failed: %s [%d]\n", strerror(errno), errno);
|
|
return 0;
|
|
}
|
|
if (fcntl(jsonrpc_fifo_read, F_SETFL, opt & (~O_NONBLOCK))==-1){
|
|
LM_ERR("cntl(F_SETFL) failed: %s [%d]\n", strerror(errno), errno);
|
|
return 0;
|
|
}
|
|
|
|
jsonrpc_reply_fifo_s = pkg_malloc(JSONRPC_MAX_FILENAME);
|
|
if (jsonrpc_reply_fifo_s==NULL) {
|
|
LM_ERR("no more private memory\n");
|
|
return 0;
|
|
}
|
|
|
|
/* init fifo reply dir buffer */
|
|
jsonrpc_reply_fifo_len = strlen(fifo_reply_dir);
|
|
memcpy( jsonrpc_reply_fifo_s, jsonrpc_fifo_reply_dir, jsonrpc_reply_fifo_len);
|
|
|
|
return fifo_stream;
|
|
}
|
|
|
|
/*! \brief Read input on fifo */
|
|
int jsonrpc_read_stream(char *b, int max, FILE *stream, int *lread)
|
|
{
|
|
int retry_cnt;
|
|
int len;
|
|
char *p;
|
|
int sstate;
|
|
int pcount;
|
|
int pfound;
|
|
int stype;
|
|
|
|
sstate = 0;
|
|
retry_cnt=0;
|
|
|
|
*lread = 0;
|
|
p = b;
|
|
pcount = 0;
|
|
pfound = 0;
|
|
stype = 0;
|
|
|
|
while(1) {
|
|
len = fread (p, 1, 1, stream);
|
|
if(len==0) {
|
|
LM_ERR("fifo server fread failed: %s\n", strerror(errno));
|
|
/* on Linux, sometimes returns ESPIPE -- give
|
|
it few more chances
|
|
*/
|
|
if (errno==ESPIPE) {
|
|
retry_cnt++;
|
|
if (retry_cnt>4)
|
|
return -1;
|
|
continue;
|
|
}
|
|
/* interrupted by signal or ... */
|
|
if ((errno==EINTR)||(errno==EAGAIN))
|
|
continue;
|
|
return -1;
|
|
}
|
|
if(*p=='"' && (sstate==0 || stype==1)) {
|
|
if(*lread>0) {
|
|
if(*(p-1)!='\\') {
|
|
sstate = (sstate+1) % 2;
|
|
stype = 1;
|
|
}
|
|
} else {
|
|
sstate = (sstate+1) % 2;
|
|
stype = 1;
|
|
}
|
|
} else if(*p=='\'' && (sstate==0 || stype==2)) {
|
|
if(*lread>0) {
|
|
if(*(p-1)!='\\') {
|
|
sstate = (sstate+1) % 2;
|
|
stype = 2;
|
|
}
|
|
} else {
|
|
sstate = (sstate+1) % 2;
|
|
stype = 2;
|
|
}
|
|
} else if(*p=='{') {
|
|
if(sstate==0) {
|
|
pfound = 1;
|
|
pcount++;
|
|
}
|
|
} else if(*p=='}') {
|
|
if(sstate==0)
|
|
pcount--;
|
|
}
|
|
*lread = *lread + 1;
|
|
if(*lread>=max-1) {
|
|
LM_WARN("input data too large (%d)\n", *lread);
|
|
return -1;
|
|
}
|
|
p++;
|
|
if(pfound==1 && pcount==0) {
|
|
*p = 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*! \brief reply fifo security checks:
|
|
*
|
|
* checks if fd is a fifo, is not hardlinked and it's not a softlink
|
|
* opened file descriptor + file name (for soft link check)
|
|
* \return 0 if ok, <0 if not */
|
|
static int jsonrpc_fifo_check(int fd, char* fname)
|
|
{
|
|
struct stat fst;
|
|
struct stat lst;
|
|
|
|
if (fstat(fd, &fst)<0){
|
|
LM_ERR("security: fstat on %s failed: %s\n", fname, strerror(errno));
|
|
return -1;
|
|
}
|
|
/* check if fifo */
|
|
if (!S_ISFIFO(fst.st_mode)){
|
|
LM_ERR("security: %s is not a fifo\n", fname);
|
|
return -1;
|
|
}
|
|
/* check if hard-linked */
|
|
if (fst.st_nlink>1){
|
|
LM_ERR("security: fifo_check: %s is hard-linked %d times\n", fname, (unsigned)fst.st_nlink);
|
|
return -1;
|
|
}
|
|
|
|
/* lstat to check for soft links */
|
|
if (lstat(fname, &lst)<0){
|
|
LM_ERR("security: lstat on %s failed: %s\n", fname, strerror(errno));
|
|
return -1;
|
|
}
|
|
if (S_ISLNK(lst.st_mode)){
|
|
LM_ERR("security: fifo_check: %s is a soft link\n", fname);
|
|
return -1;
|
|
}
|
|
/* if this is not a symbolic link, check to see if the inode didn't
|
|
* change to avoid possible sym.link, rm sym.link & replace w/ fifo race
|
|
*/
|
|
if ((lst.st_dev!=fst.st_dev)||(lst.st_ino!=fst.st_ino)){
|
|
LM_ERR("security: fifo_check: inode/dev number differ: %d %d (%s)\n",
|
|
(int)fst.st_ino, (int)lst.st_ino, fname);
|
|
return -1;
|
|
}
|
|
/* success */
|
|
return 0;
|
|
}
|
|
|
|
#define JSONRPC_REPLY_RETRIES 4
|
|
FILE *jsonrpc_open_reply_fifo(str *srpath)
|
|
{
|
|
int retries=JSONRPC_REPLY_RETRIES;
|
|
int fifofd;
|
|
FILE *file_handle;
|
|
int flags;
|
|
|
|
if ( memchr(srpath->s, '.', srpath->len)
|
|
|| memchr(srpath->s, '/', srpath->len)
|
|
|| memchr(srpath->s, '\\', srpath->len) ) {
|
|
LM_ERR("Forbidden reply fifo filename: %.*s\n",
|
|
srpath->len, srpath->s);
|
|
return 0;
|
|
}
|
|
|
|
if (jsonrpc_reply_fifo_len + srpath->len + 1 > JSONRPC_MAX_FILENAME) {
|
|
LM_ERR("Reply fifo filename too long %d\n",
|
|
jsonrpc_reply_fifo_len + srpath->len);
|
|
return 0;
|
|
}
|
|
|
|
memcpy(jsonrpc_reply_fifo_s + jsonrpc_reply_fifo_len, srpath->s, srpath->len);
|
|
jsonrpc_reply_fifo_s[jsonrpc_reply_fifo_len + srpath->len]=0;
|
|
|
|
|
|
tryagain:
|
|
/* open non-blocking to make sure that a broken client will not
|
|
* block the FIFO server forever */
|
|
fifofd=open( jsonrpc_reply_fifo_s, O_WRONLY | O_NONBLOCK );
|
|
if (fifofd==-1) {
|
|
/* retry several times if client is not yet ready for getting
|
|
feedback via a reply pipe
|
|
*/
|
|
if (errno==ENXIO) {
|
|
/* give up on the client - we can't afford server blocking */
|
|
if (retries==0) {
|
|
LM_ERR("no client at %s\n", jsonrpc_reply_fifo_s );
|
|
return 0;
|
|
}
|
|
/* don't be noisy on the very first try */
|
|
if (retries != JSONRPC_REPLY_RETRIES)
|
|
LM_DBG("mi_fifo retry countdown: %d\n", retries );
|
|
sleep_us( 80000 );
|
|
retries--;
|
|
goto tryagain;
|
|
}
|
|
/* some other opening error */
|
|
LM_ERR("open error (%s): %s\n", jsonrpc_reply_fifo_s, strerror(errno));
|
|
return 0;
|
|
}
|
|
|
|
/* security checks: is this really a fifo?, is
|
|
* it hardlinked? is it a soft link? */
|
|
if (jsonrpc_fifo_check(fifofd, jsonrpc_reply_fifo_s)<0)
|
|
goto error;
|
|
|
|
/* we want server blocking for big writes */
|
|
if ( (flags=fcntl(fifofd, F_GETFL, 0))<0) {
|
|
LM_ERR("pipe (%s): getfl failed: %s\n", jsonrpc_reply_fifo_s, strerror(errno));
|
|
goto error;
|
|
}
|
|
flags&=~O_NONBLOCK;
|
|
if (fcntl(fifofd, F_SETFL, flags)<0) {
|
|
LM_ERR("pipe (%s): setfl cntl failed: %s\n", jsonrpc_reply_fifo_s, strerror(errno));
|
|
goto error;
|
|
}
|
|
|
|
/* create an I/O stream */
|
|
file_handle=fdopen( fifofd, "w");
|
|
if (file_handle==NULL) {
|
|
LM_ERR("open error (%s): %s\n",
|
|
jsonrpc_reply_fifo_s, strerror(errno));
|
|
goto error;
|
|
}
|
|
return file_handle;
|
|
error:
|
|
close(fifofd);
|
|
return 0;
|
|
}
|
|
|
|
#define JSONRPC_BUF_IN_SIZE 8192
|
|
static void jsonrpc_run_fifo_server(FILE *fifo_stream)
|
|
{
|
|
FILE *reply_stream;
|
|
char buf_in[JSONRPC_BUF_IN_SIZE];
|
|
char buf_rpath[128];
|
|
int lread;
|
|
str scmd;
|
|
str srpath;
|
|
int nw;
|
|
|
|
while(1) {
|
|
/* update the local config framework structures */
|
|
cfg_update();
|
|
|
|
reply_stream = NULL;
|
|
lread = 0;
|
|
if(jsonrpc_read_stream(buf_in, JSONRPC_BUF_IN_SIZE,
|
|
fifo_stream, &lread)<0 || lread<=0) {
|
|
LM_DBG("failed to get the json document from fifo stream\n");
|
|
continue;
|
|
}
|
|
scmd.s = buf_in;
|
|
scmd.len = lread;
|
|
trim(&scmd);
|
|
LM_DBG("preparing to execute fifo jsonrpc [%.*s]\n", scmd.len, scmd.s);
|
|
srpath.s = buf_rpath;
|
|
srpath.len = 128;
|
|
if(jsonrpc_exec_ex(&scmd, &srpath)<0) {
|
|
LM_ERR("failed to execute the json document from fifo stream\n");
|
|
continue;
|
|
}
|
|
|
|
LM_DBG("command executed - result: [%.*s] [%d] [%p] [%.*s]\n",
|
|
srpath.len, srpath.s,
|
|
_jsonrpc_plain_reply.rcode,
|
|
_jsonrpc_plain_reply.rbody.s,
|
|
_jsonrpc_plain_reply.rbody.len, _jsonrpc_plain_reply.rbody.s);
|
|
if(srpath.len>0) {
|
|
reply_stream = jsonrpc_open_reply_fifo(&srpath);
|
|
if (reply_stream==NULL) {
|
|
LM_ERR("cannot open reply fifo: %.*s\n", srpath.len, srpath.s);
|
|
continue;
|
|
}
|
|
nw = fwrite(_jsonrpc_plain_reply.rbody.s, 1,
|
|
_jsonrpc_plain_reply.rbody.len, reply_stream);
|
|
if(nw < _jsonrpc_plain_reply.rbody.len) {
|
|
LM_ERR("failed to write the reply to fifo: %d out of %d\n",
|
|
nw, _jsonrpc_plain_reply.rbody.len);
|
|
}
|
|
fclose(reply_stream);
|
|
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void jsonrpc_fifo_process(int rank)
|
|
{
|
|
FILE *fifo_stream;
|
|
|
|
LM_DBG("new process with pid = %d created\n",getpid());
|
|
|
|
fifo_stream = jsonrpc_init_fifo_server( jsonrpc_fifo, jsonrpc_fifo_mode,
|
|
jsonrpc_fifo_uid, jsonrpc_fifo_gid, jsonrpc_fifo_reply_dir);
|
|
if ( fifo_stream==NULL ) {
|
|
LM_CRIT("The function jsonrpc_init_fifo_server returned with error!!!\n");
|
|
exit(-1);
|
|
}
|
|
|
|
jsonrpc_run_fifo_server( fifo_stream );
|
|
|
|
LM_CRIT("the function jsonroc_fifo_server returned with error!!!\n");
|
|
exit(-1);
|
|
}
|