/* * 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 #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_TCP #include #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; (eolend) && (*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 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 '::[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