/*
 * Copyright (C) 2001-2003 FhG Fokus
 * Copyright (C) 2005 iptelorg GmbH
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * 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 "../../core/dprint.h"
#include "../../core/ut.h"
#include "../../core/error.h"
#include "../../core/config.h"
#include "../../core/globals.h"
#include "../../core/mem/mem.h"
#include "../../core/mem/shm_mem.h"
#include "../../core/sr_module.h"
#include "../../core/pt.h"
#include "../../core/rpc.h"
#include "../../core/rpc_lookup.h"
#include "../../core/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), /* Positional 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 functions */

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 formatted 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 member '%.*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_exportx_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;
	unsigned int rdata;

	log_prefix_set(NULL);
	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 = rpc_lookupx(context.method, strlen(context.method), &rdata);
	if((exp == NULL) || (exp->r.function == NULL)) {
		DBG("Command %s not found\n", context.method);
		rpc_fault(&context, 500, "Command '%s' not found", context.method);
		goto consume;
	}
	if(rdata & RPC_EXEC_DELTA) {
		LM_ERR("execution of command [%s] is limited by delta [%d]\n",
				context.method, ksr_rpc_exec_delta);
		rpc_fault(&context, 500, "Command Executed Too Fast");
		goto consume;
	}

	exp->r.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("empty 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;
}


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];
	str null_value = str_init("<null string>");

	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 *);
			if(str_val.s != NULL) {
				str_val.len = strlen(str_val.s);
			} else {
				str_val = null_value;
			}
			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 *);
			if(sp != NULL && sp->s != NULL) {
				str_val = *sp;
			} else {
				str_val = null_value;
			}
			l = new_chunk_escape(&str_val, 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 = NULL;
	char *buf0;
	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 error;
			}

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

			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;
			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((buf0 = ctl_realloc(buf, buf_size)) == 0) {
			rpc_fault(ctx, 500, "Internal Server Error (No memory left)");
			ERR("No memory left\n");
			goto error;
		}
		buf = buf0;
	}

error:
	if(buf)
		ctl_free(buf);
	return -1;
}


static int rpc_rpl_printf(rpc_ctx_t *ctx, char *fmt, ...)
{
	int n, buf_size;
	char *buf;
	char *buf0;
	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((buf0 = ctl_realloc(buf, buf_size)) == 0) {
			rpc_fault(ctx, 500, "Internal Server Error (No memory left)");
			ERR("No memory left\n");
			goto err;
		}
		buf = buf0;
	}
	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;
	str null_value = str_init("<null string>");

	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 *);
					if(st.s == NULL) {
						st = null_value;
					} else {
						st.len = strlen(st.s);
					}
					c = new_chunk_escape(&st, 1);
					break;

				case 'S':
					sp = va_arg(ap, str *);
					if(sp != NULL && sp->s != NULL) {
						st = *sp;
					} else {
						st = null_value;
					}
					c = new_chunk_escape(&st, 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 = 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
