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/jsonrpc-s/jsonrpc-s_mod.c

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);
}