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/ctl/fifo_server.c

1626 lines
37 KiB

/*
* Copyright (C) 2001-2003 FhG Fokus
* Copyright (C) 2005 iptelorg GmbH
*
* 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
*
*/
/*! \file
* \brief ctl module
* \ingroup ctl
*
*/
/*! \defgroup ctl Control binrpc socket
*
* Fifo server is a very powerful tool used to access easily
* ser's internals via textual interface, similarly to
* how internals of many operating systems are accessible
* via the proc file system. This might be used for
* making ser do things for you (such as initiating new
* transaction from webpages) or inspect server's health.
*
* FIFO server allows new functionality to be registered
* with it -- thats what register_fifo_cmd is good for.
* Remember, the initialization must take place before
* forking; best in init_module functions. When a function
* is registered, it can be always evoked by sending its
* name prefixed by colon to the FIFO.
*
* There are few commands already implemented in core.
* These are 'uptime' for looking at how long the server
* is alive and 'print' for debugging purposes.
*
* Every command sent to FIFO must be sent atomically to
* avoid intermixing with other commands and MUST be
* terminated by empty line so that the server is to able
* to find its end if it does not understand the command.
*
* File test/transaction.fifo illustrates example of use
* of t_uac command (part of TM module).
*
*/
#ifdef USE_FIFO
#include <limits.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <stdarg.h>
#ifdef USE_TCP
#include <sys/socket.h>
#endif
#include "../../dprint.h"
#include "../../ut.h"
#include "../../error.h"
#include "../../config.h"
#include "../../globals.h"
#include "../../mem/mem.h"
#include "../../mem/shm_mem.h"
#include "../../sr_module.h"
#include "../../pt.h"
#include "../../rpc.h"
#include "../../tsend.h"
#include "fifo_server.h"
#include "io_listener.h"
#include "ctl.h"
#define MAX_FIFO_COMMAND 128 /* Maximum length of a FIFO server command */
#define MAX_CONSUME_BUFFER 1024 /* Buffer dimensions for FIFO server */
#define MAX_LINE_BUFFER 2048 /* Maximum parameter line length */
#define DEFAULT_REPLY_RETRIES 4 /* Default number of reply write attempts */
#define DEFAULT_REPLY_WAIT 80000 /* How long we should wait for the client, in micro seconds */
#define DEFAULT_FIFO_DIR "/tmp/" /* Where reply pipes may be opened */
#define MAX_MSG_CHUNKS 1024 /* maximum message pieces */
#define FIFO_TX_TIMEOUT 200 /* maximum time to block writing to
the fifo */
/* readline from a buffer helper */
struct readline_handle{
char* s; /* buffer start */
char* end; /* end */
char* crt; /* crt. pos */
};
enum text_flags {
CHUNK_SEEN = (1 << 0),
CHUNK_POSITIONAL = (1 << 1), /* Positinal parameter, should be followed by \n */
CHUNK_MEMBER_NAME = (1 << 2), /* Struct member name, should be followed by : */
CHUNK_MEMBER_VALUE = (1 << 3) /* Struct member value, should be followed by , if
* there is another member name and \n if not */
};
/*
* Generit text chunk. Flags attribute contains arbitrary flags
*/
struct text_chunk {
unsigned char flags;
str s;
struct text_chunk* next;
void *ctx; /* context, which must be passed along */
};
/*
* This is the parameter if rpc_struct_add
*/
struct rpc_struct_out {
struct rpc_context* ctx;
struct text_chunk* line;
};
struct rpc_struct {
struct rpc_context* ctx;
struct text_chunk* names; /* Names of elements */
struct text_chunk* values; /* Element values as strings */
struct rpc_struct* next;
};
/*
* Context structure containing state of processing
*/
typedef struct rpc_context {
char* method; /* Request method name */
char* reply_file; /* Full path and name to the reply FIFO file */
int reply_sent; /* This flag ensures that we do not send a reply twice */
int code; /* Reply code */
char* reason; /* Reason phrase */
struct text_chunk* body; /* First line to be appended as reply body */
struct text_chunk* last; /* Last body line */
struct text_chunk* strs; /* Strings to be collected at the end of processing */
struct rpc_struct* structs; /* Structures to be collected at the end of processing */
struct readline_handle read_h;
struct send_handle* send_h;
int line_no;
} rpc_ctx_t;
char* fifo_dir = DEFAULT_FIFO_DIR; /* dir where reply fifos are
allowed */
int fifo_reply_retries = DEFAULT_REPLY_RETRIES;
int fifo_reply_wait = DEFAULT_REPLY_WAIT;
static rpc_t func_param; /* Pointers to implementation of RPC funtions */
static int rpc_send (rpc_ctx_t* ctx); /* Send the reply to the client */
static void rpc_fault (rpc_ctx_t* ctx, int code, char* fmt, ...); /* Signal a failure to the client */
static int rpc_add (rpc_ctx_t* ctx, char* fmt, ...); /* Add a new piece of data to the result */
static int rpc_scan (rpc_ctx_t* ctx, char* fmt, ...); /* Retrieve request parameters */
static int rpc_rpl_printf (rpc_ctx_t* ctx, char* fmt, ...); /* Add printf-like formated data to the result set */
static int rpc_struct_add (struct text_chunk* s, char* fmt, ...); /* Create a new structure */
static int rpc_struct_scan (struct rpc_struct* s, char* fmt, ...); /* Scan attributes of a structure */
static int rpc_struct_printf(struct text_chunk* s, char* name, char* fmt, ...);
/*
* Escape string in buffer 'r' of length len. Write
* the escaped string in buffer dst. The destination
* buffer must exist and must be twice as big as the
* input buffer.
*
* Parameter all controls the set of characters to be
* escaped. If set to 1 then all characters, including
* structure delimiters, will be escaped. If set to
* 0 then only line delimiters, tab and zero will be
* escaped.
*/
static void escape(str* dst, char* r, int len, int all)
{
int i;
char* w;
if (!len) {
dst->len = 0;
return;
}
w = dst->s;
for(i = 0; i < len; i++) {
switch(r[i]) {
case '\n': *w++ = '\\'; *w++ = 'n'; break;
case '\r': *w++ = '\\'; *w++ = 'r'; break;
case '\t': *w++ = '\\'; *w++ = 't'; break;
case '\\': *w++ = '\\'; *w++ = '\\'; break;
case '\0': *w++ = '\\'; *w++ = '0'; break;
case ':':
if (all) {
*w++ = '\\';
*w++ = 'o';
} else *w++ = r[i];
break;
case ',':
if (all) {
*w++ = '\\';
*w++ = 'c';
} else *w++ = r[i];
break;
default:
*w++ = r[i];
break;
}
}
dst->len = w - dst->s;
}
/*
* Unescape the string in buffer 'r' of length len.
* The resulting string will be stored in buffer dst
* which must exist and must be at least as big as
* the source buffer. The function will update dst->len
* to the length of the resulting string.
*
* Return value 0 indicates success, -1 indicates
* formatting error.
*/
static int unescape(str* dst, char* r, int len)
{
char* w;
int i;
if (!len) {
dst->len = 0;
return 0;
}
w = dst->s;
for(i = 0; i < len; i++) {
switch(*r) {
case '\\':
r++;
i++;
switch(*r++) {
case '\\': *w++ = '\\'; break;
case 'n': *w++ = '\n'; break;
case 'r': *w++ = '\r'; break;
case 't': *w++ = '\t'; break;
case '0': *w++ = '\0'; break;
case 'c': *w++ = ':'; break; /* Structure delimiter */
case 'o': *w++ = ','; break; /* Structure delimiter */
default: return -1;
}
break;
default: *w++ = *r++; break;
}
}
dst->len = w - dst->s;
return 0;
}
/*
* Create a new text chunk, the input text will
* be escaped.
*/
struct text_chunk* new_chunk_escape(str* src, int escape_all)
{
struct text_chunk* l;
if (!src) return 0;
l = ctl_malloc(sizeof(struct text_chunk));
if (!l) {
ERR("No Memory Left\n");
return 0;
}
l->s.s = ctl_malloc(src->len * 2 + 1);
if (!l->s.s) {
ERR("No Memory Left\n");
ctl_free(l);
return 0;
}
l->next = 0;
l->flags = 0;
escape(&l->s, src->s, src->len, escape_all);
l->s.s[l->s.len] = '\0';
return l;
}
/*
* Create a new text chunk, the input text
* will not be escaped. The function returns
* 0 on an error
*/
struct text_chunk* new_chunk(str* src)
{
struct text_chunk* l;
if (!src) return 0;
l = ctl_malloc(sizeof(struct text_chunk));
if (!l) {
ERR("No Memory Left\n");
return 0;
}
l->s.s = ctl_malloc(src->len + 1);
if (!l->s.s) {
ERR("No Memory Left\n");
ctl_free(l);
return 0;
}
l->next = 0;
l->flags = 0;
memcpy(l->s.s, src->s, src->len);
l->s.len = src->len;
l->s.s[l->s.len] = '\0';
return l;
}
/*
* Create a new text chunk, the input text
* will be unescaped first.
*/
struct text_chunk* new_chunk_unescape(str* src)
{
struct text_chunk* l;
if (!src) return 0;
l = ctl_malloc(sizeof(struct text_chunk));
if (!l) {
ERR("No Memory Left\n");
return 0;
}
l->s.s = ctl_malloc(src->len + 1);
if (!l->s.s) {
ERR("No Memory Left\n");
ctl_free(l);
return 0;
}
l->next = 0;
l->flags = 0;
if (unescape(&l->s, src->s, src->len) < 0) {
ctl_free(l->s.s);
ctl_free(l);
return 0;
}
l->s.s[l->s.len] = '\0';
return l;
}
static void free_chunk(struct text_chunk* c)
{
if (c && c->s.s) ctl_free(c->s.s);
if (c) ctl_free(c);
}
static void free_struct(struct rpc_struct* s)
{
struct text_chunk* c;
if (!s) return;
while(s->names) {
c = s->names;
s->names = s->names->next;
free_chunk(c);
}
while(s->values) {
c = s->values;
s->values = s->values->next;
free_chunk(c);
}
ctl_free(s);
}
/*
* Parse a structure
*/
static struct rpc_struct* new_struct(rpc_ctx_t* ctx, str* line)
{
char* comma, *colon;
struct rpc_struct* s;
str left, right = STR_NULL, name, value;
struct text_chunk* n, *v;
if (!line->len) {
rpc_fault(ctx, 400, "Line %d Empty - Structure Expected",
ctx->line_no);
return 0;
}
s = (struct rpc_struct*)ctl_malloc(sizeof(struct rpc_struct));
if (!s) {
rpc_fault(ctx, 500, "Internal Server Error (No Memory Left)");
return 0;
}
memset(s, 0, sizeof(struct rpc_struct));
s->ctx = ctx;
left = *line;
do {
comma = q_memchr(left.s, ',', left.len);
if (comma) {
right.s = comma + 1;
right.len = left.len - (comma - left.s) - 1;
left.len = comma - left.s;
}
/* Split the record to name and value */
colon = q_memchr(left.s, ':', left.len);
if (!colon) {
rpc_fault(ctx, 400, "Colon missing in struct on line %d",
ctx->line_no);
goto err;;
}
name.s = left.s;
name.len = colon - name.s;
value.s = colon + 1;
value.len = left.len - (colon - left.s) - 1;
/* Create name chunk */
n = new_chunk_unescape(&name);
if (!n) {
rpc_fault(ctx, 400, "Error while processing struct member '%.*s' "
"on line %d", name.len, ZSW(name.s), ctx->line_no);
goto err;
}
n->next = s->names;
s->names = n;
/* Create value chunk */
v = new_chunk_unescape(&value);
if (!v) {
rpc_fault(ctx, 400, "Error while processing struct membeer '%.*s'"
" on line %d", name.len, ZSW(name.s), ctx->line_no);
goto err;
}
v->next = s->values;
s->values = v;
left = right;
} while(comma);
return s;
err:
if (s) free_struct(s);
return 0;
}
/*
* Read a line from FIFO file and store a pointer to the data in buffer 'b'
* and the legnth of the line in variable 'read'
*
* Returns -1 on error, 0 on success
*/
static int read_line(char** b, int* read, struct readline_handle* rh)
{
char* eol;
char* trim;
if (rh->crt>=rh->end){
/* end, nothing more to read */
return -1;
}
for(eol=rh->crt; (eol<rh->end) && (*eol!='\n'); eol++);
*eol=0;
trim=eol;
/* trim spaces at the end */
for(trim=eol;(trim>rh->crt) &&
((*trim=='\r')||(*trim==' ')||(*trim=='\t')); trim--){
*trim=0;
}
*b=rh->crt;
*read = (int)(trim-rh->crt);
rh->crt=eol+1;
return 0;
}
/*
* Remove directory path from filename and replace it
* with the path configured through a module parameter.
*
* The result is allocated using ctl_malloc and thus
* has to be freed using ctl_free
*/
static char *trim_filename(char * file)
{
int prefix_len, fn_len;
char *new_fn;
/* we only allow files in "/tmp" -- any directory
* changes are not welcome
*/
if (strchr(file, '.') || strchr(file, '/')
|| strchr(file, '\\')) {
ERR("Forbidden filename: %s\n"
, file);
return 0;
}
prefix_len = strlen(fifo_dir); fn_len = strlen(file);
new_fn = ctl_malloc(prefix_len + fn_len + 1);
if (new_fn == 0) {
ERR("No memory left\n");
return 0;
}
memcpy(new_fn, fifo_dir, prefix_len);
memcpy(new_fn + prefix_len, file, fn_len);
new_fn[prefix_len + fn_len] = 0;
return new_fn;
}
/* 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)
* returns 0 if ok, <0 if not
*/
static int fifo_check(int fd, char* fname)
{
struct stat fst;
struct stat lst;
if (fstat(fd, &fst) < 0) {
ERR("fstat failed: %s\n",
strerror(errno));
return -1;
}
/* check if fifo */
if (!S_ISFIFO(fst.st_mode)){
ERR("%s is not a fifo\n", fname);
return -1;
}
/* check if hard-linked */
if (fst.st_nlink > 1) {
ERR("%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) {
ERR("lstat failed: %s\n",
strerror(errno));
return -1;
}
if (S_ISLNK(lst.st_mode)) {
ERR("%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)) {
ERR("inode/dev number differ : %d %d (%s)\n",
(int)fst.st_ino, (int)lst.st_ino, fname);
return -1;
}
/* success */
return 0;
}
/*
* Open the FIFO reply file
* returns fs no on success, -1 on error
*/
static int open_reply_pipe(char *pipe_name)
{
int fifofd;
int flags;
int retries = fifo_reply_retries;
fifofd=-1;
if (!pipe_name || *pipe_name == 0) {
DBG("No file to write to about missing cmd\n");
goto error;
}
tryagain:
/* open non-blocking to make sure that a broken client will not
* block the FIFO server forever */
fifofd = open(pipe_name, 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) {
ERR("No client at %s\n", pipe_name);
goto error;
}
/* don't be noisy on the very first try */
if (retries != fifo_reply_retries) {
DBG("Retry countdown: %d\n", retries);
}
sleep_us(fifo_reply_wait);
retries--;
goto tryagain;
}
/* some other opening error */
ERR("Open error (%s): %s\n",
pipe_name, strerror(errno));
goto error;
}
/* security checks: is this really a fifo?, is
* it hardlinked? is it a soft link? */
if (fifo_check(fifofd, pipe_name) < 0) goto error;
/* we want server blocking for big writes */
if ((flags = fcntl(fifofd, F_GETFL, 0)) < 0) {
ERR("(%s): getfl failed: %s\n", pipe_name, strerror(errno));
goto error;
}
flags &= ~O_NONBLOCK;
if (fcntl(fifofd, F_SETFL, flags) < 0) {
ERR("(%s): setfl cntl failed: %s\n",
pipe_name, strerror(errno));
goto error;
}
return fifofd;
error:
if (fifofd!=-1) close(fifofd);
return -1;
}
/*
* Man FIFO routine running in the FIFO
* processes requests received
* through the FIFO file repeatedly
*/
int fifo_process(char* msg_buf, int size, int* bytes_needed, void *sh,
void** saved_state)
{
rpc_export_t* exp;
char* buf;
int line_len;
char *file_sep;
struct text_chunk* p;
struct rpc_struct* s;
int r;
int req_size;
static rpc_ctx_t context;
DBG("process_fifo: called with %d bytes, offset %d: %.*s\n",
size, (int)(long)*saved_state, size, msg_buf);
/* search for the end of the request (\n\r) */
if (size < 6){ /* min fifo request */
*bytes_needed=6-size;
return 0; /* we want more bytes, nothing processed */
}
for (r=1+(int)(long)*saved_state;r<size;r++){
if ((msg_buf[r]=='\n' || msg_buf[r]=='\r') &&
(msg_buf[r-1]=='\n'|| msg_buf[r-1]=='\r')){
/* found double cr, or double lf => end of request */
req_size=r;
goto process;
}
}
/* no end of request found => ask for more bytes */
*bytes_needed=1;
/* save current offset, to optimize search */
*saved_state=(void*)(long)(r-1);
return 0; /* we want again the whole buffer */
process:
DBG("process_fifo %d bytes request: %.*s\n",
req_size, req_size, msg_buf);
file_sep = 0;
context.method = 0;
context.reply_file = 0;
context.body = 0;
context.code = 200;
context.reason = "OK";
context.reply_sent = 0;
context.last = 0;
context.line_no = 0;
context.read_h.s=msg_buf;
context.read_h.end=msg_buf+size;
context.read_h.crt=msg_buf;
context.send_h=(struct send_handle*)sh;
/* commands must look this way ':<command>:[filename]' */
if (read_line(&buf, &line_len, &context.read_h) < 0) {
/* line breaking must have failed -- consume the rest
* and proceed to a new request
*/
ERR("Command expected\n");
goto consume;
}
context.line_no++;
if (line_len == 0) {
DBG("Empty command received\n");
goto consume;
}
if (line_len < 3) {
ERR("Command must have at least 3 chars\n");
goto consume;
}
if (*buf != CMD_SEPARATOR) {
ERR("Command must begin with %c: %.*s\n",
CMD_SEPARATOR, line_len, buf);
goto consume;
}
context.method = buf + 1;
file_sep = strchr(context.method, CMD_SEPARATOR);
if (file_sep == NULL) {
ERR("File separator missing\n");
goto consume;
}
if (file_sep == context.method) {
ERR("Empty command\n");
goto consume;
}
if (*(file_sep + 1) == 0) context.reply_file = NULL;
else {
context.reply_file = file_sep + 1;
context.reply_file = trim_filename(context.reply_file);
if (context.reply_file == 0) {
ERR("Trimming filename\n");
goto consume;
}
}
/* make command zero-terminated */
*file_sep = 0;
exp = find_rpc_export(context.method, 0);
if (!exp || !exp->function) {
DBG("Command %s not found\n", context.method);
rpc_fault(&context, 500, "Command '%s' not found", context.method);
goto consume;
}
exp->function(&func_param, &context);
consume:
if (!context.reply_sent) {
rpc_send(&context);
}
if (context.reply_file) {
ctl_free(context.reply_file);
context.reply_file = 0;
}
/* Collect garbage (unescaped strings and structures) */
while(context.strs) {
p = context.strs;
context.strs = context.strs->next;
free_chunk(p);
}
while(context.structs) {
s = context.structs;
context.structs = context.structs->next;
free_struct(s);
}
*bytes_needed=0;
DBG("Command consumed\n");
DBG("process_fifo: returning %d, bytes_needed 0\n", req_size+1);
return req_size+1; /* all was processed (including terminating \n)*/
}
/*
* Initialze the FIFO fd
* Make sure that we can create and open the FIFO file and
* make it secure. This function must be executed from mod_init.
* This ensures that it has sufficient privileges.
*/
int init_fifo_fd(char* fifo, int fifo_mode, int fifo_uid, int fifo_gid,
int* fifo_write)
{
struct stat filestat;
int n;
long opt;
int fifo_read = -1;
if (fifo == NULL) {
ERR("null fifo: no fifo will be opened\n");
/* error null fifo */
return -1;
}
if (strlen(fifo) == 0) {
ERR("emtpy fifo: fifo disabled\n");
return -1;
}
DBG("Opening fifo...\n");
n = stat(fifo, &filestat);
if (n == 0) {
/* FIFO exist, delete it (safer) */
if (unlink(fifo) < 0) {
ERR("Cannot delete old fifo (%s):"
" %s\n", fifo, strerror(errno));
return -1;
}
} else if (n < 0 && errno != ENOENT) {
ERR("FIFO stat failed: %s\n",
strerror(errno));
}
/* create FIFO ... */
if ((mkfifo(fifo, fifo_mode) < 0)) {
ERR("Can't create FIFO: "
"%s (mode=%d)\n",
strerror(errno), fifo_mode);
return -1;
}
DBG("FIFO created @ %s\n", fifo );
if ((chmod(fifo, fifo_mode) < 0)) {
ERR("Can't chmod FIFO: %s (mode=%d)\n",
strerror(errno), fifo_mode);
return -1;
}
if ((fifo_uid != -1) || (fifo_gid != -1)) {
if (chown(fifo, fifo_uid, fifo_gid) < 0) {
ERR("Failed to change the owner/group for %s to %d.%d; %s[%d]\n",
fifo, fifo_uid, fifo_gid, strerror(errno), errno);
return -1;
}
}
DBG("fifo %s opened, mode=%d\n", fifo, fifo_mode);
fifo_read = open(fifo, O_RDONLY | O_NONBLOCK, 0);
if (fifo_read < 0) {
ERR("fifo_read did not open: %s\n", strerror(errno));
return -1;
}
/* make sure the read fifo will not close */
*fifo_write = open(fifo, O_WRONLY | O_NONBLOCK, 0);
if (*fifo_write < 0) {
ERR("fifo_write did not open: %s\n", strerror(errno));
close(fifo_read);
return -1;
}
/* set read fifo blocking mode */
if ((opt = fcntl(fifo_read, F_GETFL)) == -1) {
ERR("fcntl(F_GETFL) failed: %s [%d]\n", strerror(errno), errno);
close(fifo_read);
close(*fifo_write);
*fifo_write = -1;
return -1;
}
if (fcntl(fifo_read, F_SETFL, opt & (~O_NONBLOCK)) == -1) {
ERR("fcntl(F_SETFL) failed: %s [%d]\n", strerror(errno), errno);
close(fifo_read);
close(*fifo_write);
*fifo_write = -1;
return -1;
}
return fifo_read;
}
int fifo_rpc_init()
{
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_array_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;
return 0;
}
/*
* Close and unlink the FIFO file
*/
void destroy_fifo(int read_fd, int w_fd, char* fname)
{
if (read_fd!=-1)
close(read_fd);
if(w_fd!=-1)
close(w_fd);
/* if FIFO was created, delete it */
if (fname && strlen(fname)) {
if (unlink(fname) < 0) {
WARN("Cannot delete fifo (%s):"
" %s\n", fname, strerror(errno));
}
}
}
#define REASON_BUF_LEN 1024
/*
* An error occurred, signal it to the client
*/
static void rpc_fault(rpc_ctx_t* ctx, int code, char* fmt, ...)
{
static char buf[REASON_BUF_LEN];
va_list ap;
ctx->code = code;
va_start(ap, fmt);
vsnprintf(buf, REASON_BUF_LEN, fmt, ap);
va_end(ap);
ctx->reason = buf;
}
static inline int safe_write(FILE* f, char* fmt, ...)
{
va_list ap;
if (!*fmt) return 0;
va_start(ap, fmt);
retry:
/* First line containing code and reason phrase */
if (vfprintf(f, fmt, ap) <= 0) {
ERR("fifo write error: %s\n", strerror(errno));
if ((errno == EINTR) || (errno == EAGAIN) || (errno == EWOULDBLOCK)) {
goto retry;
}
va_end(ap);
return -1;
}
va_end(ap);
return 0;
}
inline static int build_iovec(rpc_ctx_t* ctx, struct iovec* v, int v_size)
{
struct text_chunk* p;
int r_c_len;
int r;
/* reason code */
v[0].iov_base=int2str(ctx->code, &r_c_len);
v[0].iov_len=r_c_len;
v[1].iov_base=" ";
v[1].iov_len=1;
/* reason txt */
v[2].iov_base=ctx->reason;
v[2].iov_len=strlen(ctx->reason);
v[3].iov_base="\n";
v[3].iov_len=1;
r=4;
/* Send the body */
while(ctx->body) {
p = ctx->body;
ctx->body = ctx->body->next;
if (p->s.len){
if (r>=v_size) goto error_overflow;
v[r].iov_base=p->s.s;
v[r].iov_len=p->s.len;
r++;
}
if (p->flags & CHUNK_POSITIONAL) {
if (r>=v_size) goto error_overflow;
v[r].iov_base="\n";
v[r].iov_len=1;
r++;
} else if (p->flags & CHUNK_MEMBER_NAME) {
if (r>=v_size) goto error_overflow;
v[r].iov_base=":";
v[r].iov_len=1;
r++;
} else if (p->flags & CHUNK_MEMBER_VALUE) {
if (p->next && p->next->flags & CHUNK_MEMBER_NAME) {
if (r>=MAX_MSG_CHUNKS) goto error_overflow;
v[r].iov_base=",";
v[r].iov_len=1;
r++;
} else {
if (r>=v_size) goto error_overflow;
v[r].iov_base="\n";
v[r].iov_len=1;
r++;
}
}
free_chunk(p);
}
return r;
error_overflow:
ERR("too many message chunks, iovec buffer overflow: %d/%d\n", r,
MAX_MSG_CHUNKS);
return -1;
}
/*
* Send a reply, either positive or negative, to the client
*/
static int rpc_send(rpc_ctx_t* ctx)
{
struct iovec v[MAX_MSG_CHUNKS];
int f;
int n;
int ret;
/* Send the reply only once */
if (ctx->reply_sent) return 1;
else ctx->reply_sent = 1;
if ((n=build_iovec(ctx, v, MAX_MSG_CHUNKS))<0)
goto error;
if (ctx->send_h->type==S_FIFO){
/* Open the reply file */
f = open_reply_pipe(ctx->reply_file);
if (f == -1) {
ERR("No reply pipe %s\n", ctx->reply_file);
return -1;
}
ret=tsend_dgram_ev(f, v, n, FIFO_TX_TIMEOUT);
close(f);
}else{
ret=sock_send_v(ctx->send_h, v, n);
}
return (ret>=0)?0:-1;
error:
ERR("rpc_send fifo error\n");
return -1;
}
/*
* Add a chunk to reply
*/
static void append_chunk(rpc_ctx_t* ctx, struct text_chunk* l)
{
if (!ctx->last) {
ctx->body = l;
ctx->last = l;
} else {
ctx->last->next = l;
ctx->last = l;
}
}
/*
* Convert a value to data chunk and add it to reply
*/
static int print_value(rpc_ctx_t* ctx, char fmt, va_list* ap)
{
struct text_chunk* l;
str str_val;
str* sp;
char buf[256];
switch(fmt) {
case 'd':
case 't':
str_val.s = int2str(va_arg(*ap, int), &str_val.len);
l = new_chunk(&str_val);
if (!l) {
rpc_fault(ctx, 500, "Internal server error while processing"
" line %d", ctx->line_no);
goto err;
}
break;
case 'f':
str_val.s = buf;
str_val.len = snprintf(buf, 256, "%f", va_arg(*ap, double));
if (str_val.len < 0) {
rpc_fault(ctx, 400, "Error While Converting double");
ERR("Error while converting double\n");
goto err;
}
l = new_chunk(&str_val);
if (!l) {
rpc_fault(ctx, 500, "Internal Server Error, line %d",
ctx->line_no);
goto err;
}
break;
case 'b':
str_val.len = 1;
str_val.s = ((va_arg(*ap, int) == 0) ? "0" : "1");
l = new_chunk(&str_val);
if (!l) {
rpc_fault(ctx, 500, "Internal Server Error, line %d",
ctx->line_no);
goto err;
}
break;
case 's':
str_val.s = va_arg(*ap, char*);
str_val.len = strlen(str_val.s);
l = new_chunk_escape(&str_val, 0);
if (!l) {
rpc_fault(ctx, 500, "Internal Server Error, line %d",
ctx->line_no);
goto err;
}
break;
case 'S':
sp = va_arg(*ap, str*);
l = new_chunk_escape(sp, 0);
if (!l) {
rpc_fault(ctx, 500, "Internal Server Error, line %d",
ctx->line_no);
goto err;
}
break;
default:
rpc_fault(ctx, 500, "Bug In SER (Invalid formatting character %c)", fmt);
ERR("Invalid formatting character\n");
goto err;
}
l->flags |= CHUNK_POSITIONAL;
append_chunk(ctx, l);
return 0;
err:
return -1;
}
static int rpc_add(rpc_ctx_t* ctx, char* fmt, ...)
{
void** void_ptr;
va_list ap;
str s = {"", 0};
struct text_chunk* l;
va_start(ap, fmt);
while(*fmt) {
if (*fmt == '{' || *fmt == '[') {
void_ptr = va_arg(ap, void**);
l = new_chunk(&s);
if (!l) {
rpc_fault(ctx, 500, "Internal Server Error");
goto err;
}
l->ctx=ctx;
append_chunk(ctx, l);
*void_ptr = l;
} else {
if (print_value(ctx, *fmt, &ap) < 0) goto err;
}
fmt++;
}
va_end(ap);
return 0;
err:
va_end(ap);
return -1;
}
#define RPC_BUF_SIZE 1024
static int rpc_struct_printf(struct text_chunk* c, char* name, char* fmt, ...)
{
int n, buf_size;
char* buf;
va_list ap;
str s, nm;
struct text_chunk* l, *m;
rpc_ctx_t* ctx;
ctx=(rpc_ctx_t*)c->ctx;
buf = (char*)ctl_malloc(RPC_BUF_SIZE);
if (!buf) {
rpc_fault(ctx, 500, "Internal Server Error (No memory left)");
ERR("No memory left\n");
return -1;
}
buf_size = RPC_BUF_SIZE;
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) {
nm.s = name;
nm.len = strlen(name);
m = new_chunk_escape(&nm, 1); /* Escape all characters, including : and , */
if (!m) {
rpc_fault(ctx, 500, "Internal Server Error");
goto err;
}
s.s = buf;
s.len = n;
l = new_chunk_escape(&s, 1);
if (!l) {
rpc_fault(ctx, 500, "Internal Server Error");
free_chunk(m);
ERR("Error while creating text_chunk structure");
goto err;
}
l->flags |= CHUNK_MEMBER_VALUE;
l->next = c->next;
c->next = l;
if (c == ctx->last) ctx->last = l;
m->flags |= CHUNK_MEMBER_NAME;
m->next = c->next;
c->next = m;
if (c == ctx->last) ctx->last = m;
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 = ctl_realloc(buf, buf_size)) == 0) {
rpc_fault(ctx, 500, "Internal Server Error (No memory left)");
ERR("No memory left\n");
goto err;
}
}
return 0;
err:
if (buf) ctl_free(buf);
return -1;
}
static int rpc_rpl_printf(rpc_ctx_t* ctx, char* fmt, ...)
{
int n, buf_size;
char* buf;
va_list ap;
str s;
struct text_chunk* l;
buf = (char*)ctl_malloc(RPC_BUF_SIZE);
if (!buf) {
rpc_fault(ctx, 500, "Internal Server Error (No memory left)");
ERR("No memory left\n");
return -1;
}
buf_size = RPC_BUF_SIZE;
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) {
s.s = buf;
s.len = n;
l = new_chunk_escape(&s, 0);
if (!l) {
rpc_fault(ctx, 500, "Internal Server Error");
ERR("Error while creating text_chunk structure");
goto err;
}
append_chunk(ctx, l);
ctl_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 = ctl_realloc(buf, buf_size)) == 0) {
rpc_fault(ctx, 500, "Internal Server Error (No memory left)");
ERR("No memory left\n");
goto err;
}
}
return 0;
err:
if (buf) ctl_free(buf);
return -1;
}
static int rpc_scan(rpc_ctx_t* ctx, char* fmt, ...)
{
struct text_chunk* l;
struct rpc_struct* s;
int* int_ptr;
char** char_ptr;
str* str_ptr;
double* double_ptr;
void** void_ptr;
int read;
str line;
int nofault;
int modifiers;
va_list ap;
va_start(ap, fmt);
nofault = 0;
read = 0;
modifiers=0;
while(*fmt) {
if (read_line(&line.s, &line.len, &ctx->read_h) < 0) {
va_end(ap);
return read;
}
ctx->line_no++;
switch(*fmt) {
case '*': /* start of optional parameters */
nofault = 1;
modifiers++;
break;
case 'b': /* Bool */
case 't': /* Date and time */
case 'd': /* Integer */
if (!line.len) {
if(nofault==0)
rpc_fault(ctx, 400, "Invalid parameter value on line %d",
ctx->line_no);
goto error;
}
int_ptr = va_arg(ap, int*);
*int_ptr = strtol(line.s, 0, 0);
break;
case 'f': /* double */
if (!line.len) {
if(nofault==0)
rpc_fault(ctx, 400, "Invalid parameter value on line %d",
ctx->line_no);
goto error;
}
double_ptr = va_arg(ap, double*);
*double_ptr = strtod(line.s, 0);
break;
case 's': /* zero terminated string */
case 'S': /* str structure */
l = new_chunk_unescape(&line);
if (!l) {
if(nofault==0) {
rpc_fault(ctx, 500, "Internal Server Error");
ERR("Not enough memory\n");
}
goto error;
}
/* Make sure it gets released at the end */
l->next = ctx->strs;
ctx->strs = l;
if (*fmt == 's') {
char_ptr = va_arg(ap, char**);
*char_ptr = l->s.s;
} else {
str_ptr = va_arg(ap, str*);
*str_ptr = l->s;
}
break;
case '{':
void_ptr = va_arg(ap, void**);
s = new_struct(ctx, &line);
if (!s) goto error;
s->next = ctx->structs;
ctx->structs = s;
*void_ptr = s;
break;
default:
ERR("Invalid parameter type in formatting string: %c\n", *fmt);
rpc_fault(ctx, 500, "Server Internal Error (Invalid Formatting Character '%c')", *fmt);
goto error;
}
fmt++;
read++;
}
va_end(ap);
return read-modifiers;
error:
va_end(ap);
return -(read-modifiers);
}
static int rpc_struct_add(struct text_chunk* s, char* fmt, ...)
{
static char buf[MAX_LINE_BUFFER];
str st, *sp;
void** void_ptr;
va_list ap;
struct text_chunk* m, *c;
rpc_ctx_t* ctx;
ctx=(rpc_ctx_t*)s->ctx;
va_start(ap, fmt);
while(*fmt) {
/* Member name escaped */
st.s = va_arg(ap, char*);
st.len = strlen(st.s);
m = new_chunk_escape(&st, 1); /* Escape all characters, including : and , */
if (!m) {
rpc_fault(ctx, 500, "Internal Server Error");
goto err;
}
m->flags |= CHUNK_MEMBER_NAME;
if(*fmt=='{' || *fmt=='[') {
void_ptr = va_arg(ap, void**);
m->ctx=ctx;
append_chunk(ctx, m);
*void_ptr = m;
} else {
switch(*fmt) {
case 'd':
case 't':
st.s = int2str(va_arg(ap, int), &st.len);
c = new_chunk(&st);
break;
case 'f':
st.s = buf;
st.len = snprintf(buf, 256, "%f", va_arg(ap, double));
if (st.len < 0) {
rpc_fault(ctx, 400, "Error While Converting double");
ERR("Error while converting double\n");
goto err;
}
c = new_chunk(&st);
break;
case 'b':
st.len = 1;
st.s = ((va_arg(ap, int) == 0) ? "0" : "1");
c = new_chunk(&st);
break;
case 's':
st.s = va_arg(ap, char*);
st.len = strlen(st.s);
c = new_chunk_escape(&st, 1);
break;
case 'S':
sp = va_arg(ap, str*);
c = new_chunk_escape(sp, 1);
break;
default:
rpc_fault(ctx, 500, "Bug In SER (Invalid formatting character %c)",
*fmt);
ERR("Invalid formatting character\n");
goto err;
}
if (!c) {
rpc_fault(ctx, 500, "Internal Server Error");
goto err;
}
c->flags |= CHUNK_MEMBER_VALUE;
c->next = s->next;
s->next = c;
if (s == ctx->last) ctx->last = c;
m->next = s->next;
s->next = m;
if (s == ctx->last) ctx->last = m;
}
fmt++;
}
va_end(ap);
return 0;
err:
if (m) free_chunk(m);
va_end(ap);
return -1;
}
static int find_member(struct text_chunk** value, struct rpc_struct* s, str* member_name)
{
struct text_chunk* n, *v;
n = s->names;
v = s->values;
while(n) {
if (member_name->len == n->s.len &&
!strncasecmp(member_name->s, n->s.s, n->s.len)) {
if (n->flags & CHUNK_SEEN) goto skip;
else {
*value = v;
n->flags |= CHUNK_SEEN;
return 0;
}
}
skip:
n = n->next;
v = v->next;
}
return 1;
}
static int rpc_struct_scan(struct rpc_struct* s, char* fmt, ...)
{
struct text_chunk* val;
va_list ap;
int* int_ptr;
double* double_ptr;
char** char_ptr;
str* str_ptr;
str member_name;
int ret, read;
read = 0;
va_start(ap, fmt);
while(*fmt) {
member_name.s = va_arg(ap, char*);
member_name.len = strlen(member_name.s);
ret = find_member(&val, s, &member_name);
if (ret > 0) {
va_end(ap);
return read;
}
switch(*fmt) {
case 'b': /* Bool */
case 't': /* Date and time */
case 'd': /* Integer */
int_ptr = va_arg(ap, int*);
if (!val->s.len) {
rpc_fault(s->ctx, 400, "Invalid Parameter Value");
goto error;
}
/* String in text_chunk is always zero terminated */
*int_ptr = strtol(val->s.s, 0, 0);
break;
case 'f': /* double */
double_ptr = va_arg(ap, double*);
if (!val->s.len) {
rpc_fault(s->ctx, 400, "Invalid Parameter Value");
goto error;
}
/* String in text_chunk is always zero terminated */
*double_ptr = strtod(val->s.s, 0);
break;
case 's': /* zero terminated string */
char_ptr = va_arg(ap, char**);
/* String in text_chunk is always zero terminated */
*char_ptr = val->s.s;
break;
case 'S': /* str structure */
str_ptr = va_arg(ap, str*);
str_ptr->len = strlen(str_ptr->s);
*str_ptr = val->s;
break;
default:
rpc_fault(s->ctx, 500, "Invalid character in formatting string '%c'", *fmt);
ERR("Invalid parameter type in formatting string: %c\n", *fmt);
goto error;
}
fmt++;
read++;
}
va_end(ap);
return read;
error:
va_end(ap);
return -read;
}
#endif