unixsock_server.c
50a0659f
 /*
  * $Id$
  *
  * UNIX Domain Socket Server
  *
53c7e0f1
  * Copyright (C) 2001-2004 FhG Fokus
50a0659f
  *
  * This file is part of ser, a free SIP server.
  *
  * ser 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
  *
  * For a license to use the ser software under conditions
  * other than those described here, or to purchase support for this
  * software, please contact iptel.org by e-mail at the following addresses:
  *    info@iptel.org
  *
  * ser 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
851b529f
 /* History:
  *              created by janakj
  *  2004-03-03  added tcp init code (andrei)
71fd3ebd
  *  2004-04-29  added chmod(sock_perm) & chown(sock_user,sock_group)  (andrei)
851b529f
  */
50a0659f
 
 #include <unistd.h>
 #include <errno.h>
 #include <string.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
4421b319
 #include <signal.h>
50a0659f
 #include <stdarg.h>
4421b319
 #include <time.h>
037024d7
 #include <fcntl.h>
4421b319
 #include "config.h"
50a0659f
 #include "ut.h"
 #include "globals.h"
 #include "trim.h"
 #include "pt.h"
 #include "sr_module.h"
 #include "mem/mem.h"
 #include "fifo_server.h" /* CMD_SEPARATOR */
 #include "unixsock_server.h"
372ed8d2
 #include "tsend.h"
50a0659f
 
852abad5
 
 /* AF_LOCAL is not defined on solaris */
 #if !defined(AF_LOCAL)
 #define AF_LOCAL AF_UNIX
 #endif
 #if !defined(PF_LOCAL)
 #define PF_LOCAL PF_UNIX
 #endif
 
 
 /* solaris doesn't have SUN_LEN */
 #ifndef SUN_LEN
 #define SUN_LEN(sa)	 ( strlen((sa)->sun_path) + \
 					 (size_t)(((struct sockaddr_un*)0)->sun_path) )
 #endif
 
 
50a0659f
 #define UNIXSOCK_BUF_SIZE BUF_SIZE
 
 char* unixsock_name = 0;
 int unixsock_children = 1;
53c7e0f1
 int unixsock_tx_timeout = 2000; /* Timeout for sending replies in milliseconds */
6b17cae3
 
50a0659f
 
037024d7
 static int rx_sock, tx_sock;
22e71358
 static struct unixsock_cmd* cmd_list = 0;
50a0659f
 static char reply_buf[UNIXSOCK_BUF_SIZE];
 static str reply_pos;
 static struct sockaddr_un reply_addr;
d7a3fdea
 static unsigned int reply_addr_len;
50a0659f
 
4421b319
 static time_t up_since;
 static char up_since_ctime[MAX_CTIME_LEN];
50a0659f
 
4421b319
 
 #define PRINT_CMD "print"     /* Diagnostic command */
 #define VERSION_CMD "version" /* Print the version of the server */
 #define UPTIME_CMD "uptime"   /* Print server's uptime */
 #define WHICH_CMD "which"     /* Print available FIFO commands */
 #define PS_CMD "ps"           /* Print server's process table */
 #define ARG_CMD "arg"         /* Print server's command line arguments */
 #define PWD_CMD "pwd"         /* Get the current working directory */
 #define KILL_CMD "kill"       /* Kill the server */
 
 
 /* 
  * Diagnostic and hello-world command 
  */
 static int print_cmd(str* msg)
 {
 	str line;
 	int ret;
 
 	ret = 0;
 
 	if (unixsock_read_line(&line, msg) < 0) {
 		unixsock_reply_asciiz("500 Error while reading text\n");
 		ret = -1;
 		goto end;
 	}
 
 	if (unixsock_reply_printf("200 OK\n%.*s\n", line.len, ZSW(line.s)) < 0) {
 		unixsock_reply_reset();
 		unixsock_reply_asciiz("500 Error while sending reply\n");
 		ret = -1;
 	}
 
  end:
 	if (unixsock_reply_send() < 0) ret = -1;
 	return ret;
 }
 
 
 /*
  * Print the version of the server
  */
 static int version_cmd(str* msg)
 {
 	int ret;
 
 	ret = 0;
 	if (unixsock_reply_asciiz("200 OK\n" SERVER_HDR CRLF) < 0) ret = -1;
 	if (unixsock_reply_send() < 0) ret = -1;
 	return ret;
 }
 
 
 static int uptime_cmd(str* msg)
 {
 	time_t now;
 	int ret;
 
 	time(&now);
 	ret = 0;
 	
 	if (unixsock_reply_printf("200 OK\nNow: %sUp Since: %sUp time: %.0f [sec]\n",
 				  ctime(&now), up_since_ctime, difftime(now, up_since)) < 0) {
 		unixsock_reply_reset();
 		unixsock_reply_asciiz("500 Error while printing reply\n");
 		ret = -1;
 	}
 	
 	if (unixsock_reply_send() < 0) {
 		ret = -1;
 	}
 	
 	return ret;
 }
 
 
 static int which_cmd(str* msg)
 {
 	struct unixsock_cmd* c;
 	int ret;
 
 	ret = 0;
 	unixsock_reply_asciiz("200 OK\n");
 
 	for(c = cmd_list; c; c = c->next) {
 		if (unixsock_reply_printf("%s\n", c->name) < 0) {
 			unixsock_reply_reset();
 			unixsock_reply_asciiz("500 Error while creating reply\n");
 			ret = -1;
 			break;
 		}
 	}
 	
 	if (unixsock_reply_send() < 0) {
 		ret = -1;
 	}
 	return ret;
 }
 
 
 static int ps_cmd(str* msg)
 {
 	int p, ret;
 
 	ret = 0;
 	unixsock_reply_asciiz("200 OK\n");
 	for (p = 0; p < process_count(); p++) {
 		if (unixsock_reply_printf("%d\t%d\t%s\n", p, pt[p].pid, pt[p].desc) < 0) {
 			unixsock_reply_reset();
 			unixsock_reply_asciiz("500 Error while printing reply\n");
 			ret = -1;
 			break;
 		}
 	}
 	
 	if (unixsock_reply_send() < 0) {
 		ret = -1;
 	}
 	return ret;
 }
 
 
 static int pwd_cmd(str* msg)
 {
 	char *cwd_buf;
 	int max_len, ret;
 
 	max_len = pathmax();
 	cwd_buf = pkg_malloc(max_len);
 	ret = 0;
 	if (!cwd_buf) {
 		LOG(L_ERR, "pwd_cmd: No memory left\n");
 		unixsock_reply_asciiz("500 No Memory Left\n");
 		ret = -1;
 	}
 
 	if (getcwd(cwd_buf, max_len)) {
 		if (unixsock_reply_printf("200 OK\n%s\n", cwd_buf) < 0) {
 			unixsock_reply_reset();
 			unixsock_reply_asciiz("500 Error while sending reply\n");
 			ret = -1;
 		}
 	} else {
 		unixsock_reply_asciiz("500 getcwd Failed\n");
 		ret = -1;
 	}
 
 	pkg_free(cwd_buf);
 	if (unixsock_reply_send() < 0) {
 		ret = -1;
 	}
 	return ret;
 }
 
 
 static int arg_cmd(str* msg)
 {
 	int p, ret;
 
 	ret = 0;
 	unixsock_reply_asciiz("200 OK\n");
 	for (p = 0; p < my_argc; p++) {
 		if (unixsock_reply_printf("%s\n", my_argv[p]) < 0) {
 			unixsock_reply_reset();
 			unixsock_reply_asciiz("500 Could not create reply\n");
 			ret = -1;
 			break;
 		}
 	}
 			
 	if (unixsock_reply_send() < 0) {
 		ret = -1;
 	}
 	return ret;
 }
 
 
 static int kill_cmd(str* msg)
 {
 	unixsock_reply_asciiz("200 Killing now\n");
 	unixsock_reply_send();
 	kill(0, SIGTERM);
 	return 0;
 }
 
 
 static int register_core_commands(void)
 {
 	if (unixsock_register_cmd(PRINT_CMD, print_cmd) < 0) {
 		return -1;
 	}
 
 	if (unixsock_register_cmd(VERSION_CMD, version_cmd) < 0) {
 		return -1;
 	}
 
 	if (unixsock_register_cmd(UPTIME_CMD, uptime_cmd) < 0) {
 		return -1;
 	}
 
 	if (unixsock_register_cmd(WHICH_CMD, which_cmd) < 0) {
 		return -1;
 	}
 
 	if (unixsock_register_cmd(PS_CMD, ps_cmd) < 0) {
 		return -1;
 	}
 
 	if (unixsock_register_cmd(PWD_CMD, pwd_cmd) < 0) {
 		return -1;
 	}
 
 	if (unixsock_register_cmd(ARG_CMD, arg_cmd) < 0) {
 		return -1;
 	}
 
 	if (unixsock_register_cmd(KILL_CMD, kill_cmd) < 0) {
 		return -1;
 	}
 	return 0;
 }
50a0659f
 
 
 /*
  * Create and bind local socket
  */
b3fef92b
 int init_unixsock_socket(void)
50a0659f
 {
 	struct sockaddr_un addr;
037024d7
 	int len, flags;
50a0659f
 
b3fef92b
 	if (unixsock_name == 0) {
 		DBG("init_unixsock_socket: No unix domain socket"
50a0659f
 		    " will be opened\n");
 		return 1;
 	}
 
b3fef92b
 	len = strlen(unixsock_name);
50a0659f
 	if (len == 0) {
b3fef92b
 		DBG("init_unixsock_socket: Unix domain socket server disabled\n");
50a0659f
 		return 1;
 	} else if (len > 107) {
71fd3ebd
 		LOG(L_ERR, "ERROR: init_unixsock_socket: Socket name too long\n");
50a0659f
 		return -1;
 	}
 
b3fef92b
 	DBG("init_unixsock_socket: Initializing Unix domain socket server @ %s\n", 
 	    unixsock_name);
50a0659f
 
b3fef92b
 	if (unlink(unixsock_name) == -1) {
50a0659f
 		if (errno != ENOENT) {
71fd3ebd
 			LOG(L_ERR, "ERROR: init_unixsock_socket: Error while unlinking "
b3fef92b
 			    "old socket (%s): %s\n", unixsock_name, strerror(errno));
50a0659f
 			return -1;
 		}
 	}
 
037024d7
 	rx_sock = socket(PF_LOCAL, SOCK_DGRAM, 0);
 	if (rx_sock == -1) {
71fd3ebd
 		LOG(L_ERR, "ERROR: init_unixsock_socket: Cannot create RX "
 				"socket: %s\n", strerror(errno));
50a0659f
 		return -1;
 	}
 
 	memset(&addr, 0, sizeof(addr));
 	addr.sun_family = PF_LOCAL;
b3fef92b
 	memcpy(addr.sun_path, unixsock_name, len);
50a0659f
 
037024d7
 	if (bind(rx_sock, (struct sockaddr*)&addr, SUN_LEN(&addr)) == -1) {
71fd3ebd
 		LOG(L_ERR, "ERROR: init_unixsock_socket: bind: %s\n", strerror(errno));
037024d7
 		goto err_rx;
 	}
71fd3ebd
 	/* try to change the permissions */
 	if (sock_mode){ /* sock_mode==0 doesn't make sense, nobody can read/write*/
 		if (chmod(unixsock_name, sock_mode)<0){
 			LOG(L_ERR, "ERROR: init_unixsock_socket: failed to change the"
 					" permissions for %s to %04o: %s[%d]\n",
 					unixsock_name, sock_mode, strerror(errno), errno);
 			goto err_rx;
 		}
 	}
 	/* try to change the ownership */
 	if ((sock_uid!=-1) || (sock_gid!=-1)){
 		if (chown(unixsock_name, sock_uid, sock_gid)<0){
 			LOG(L_ERR, "ERROR: init_unixsock_socket: failed to change the"
 					" owner/group for %s  to %d.%d; %s[%d]\n",
 					unixsock_name, sock_uid, sock_gid, strerror(errno), errno);
 			goto err_rx;
 		}
 	}
037024d7
 
 	tx_sock = socket(PF_LOCAL, SOCK_DGRAM, 0);
 	if (tx_sock == -1) {
71fd3ebd
 		LOG(L_ERR, "ERROR: init_unixsock_socket: Cannot create TX socket:"
 				" %s\n", strerror(errno));
037024d7
 		goto err_rx;
50a0659f
 	}
 
037024d7
 	     /* Turn non-blocking mode on */
 	flags = fcntl(tx_sock, F_GETFL);
 	if (flags == -1){
71fd3ebd
 		LOG(L_ERR, "ERROR: init_unixsock_socket: fcntl failed: %s\n",
037024d7
 		    strerror(errno));
 		goto err_both;
 	}
 		
 	if (fcntl(tx_sock, F_SETFL, flags | O_NONBLOCK) == -1) {
71fd3ebd
 		LOG(L_ERR, "ERROR: init_unixsock_socket: fcntl: "
 				"set non-blocking failed: %s\n", strerror(errno));
037024d7
 		goto err_both;
 	}
 	
b3fef92b
 	return 1;
037024d7
  err_both:
 	close(tx_sock);
  err_rx:
 	close(rx_sock);
 	return -1;
50a0659f
 }
 
 
 static struct unixsock_cmd* lookup_cmd(str* cmd)
 {
 	struct unixsock_cmd* c;
 
 	for(c = cmd_list; c; c = c->next) {
 		if ((cmd->len == c->name.len) &&
 		    (strncasecmp(c->name.s, cmd->s, cmd->len) == 0)) {
 			return c;
 		}
 	}
 	return 0;
 }
 
 
 static int parse_cmd(str* res, str* buffer)
 {
 	char* cmd_end;
 
 	if (!res || !buffer) {
 		LOG(L_ERR, "parse_cmd: Invalid parameter value\n");
 		return -1;
 	}
 
 	if (buffer->len < 3) {
 		LOG(L_ERR, "parse_cmd: Message too short\n");
 		return -1;
 	}
 
 	if (buffer->s[0] != CMD_SEPARATOR) {
 		LOG(L_ERR, "parse_cmd: Command must start with %c\n", 
 		    CMD_SEPARATOR);
 		return -1;
 	}
 	
 	cmd_end = q_memchr(buffer->s + 1, CMD_SEPARATOR, buffer->len - 1);
 	if (!cmd_end) {
 		LOG(L_ERR, "parse_cmd: Closing '%c' missing\n", CMD_SEPARATOR);
 		return -1;
 	}
 
 	res->s = buffer->s + 1;
 	res->len = cmd_end - res->s;
 	return 0;
 } 
 
 
 static void skip_line(str* buffer)
 {
 	if (!buffer) return;
 
 	     /* Find \n */
 	while (buffer->len && (buffer->s[0] != '\n')) {
 		buffer->s++;
 		buffer->len--;
 	}
 
 	if (buffer->len) {
 		buffer->s++;
 		buffer->len--;
 	}
 
 	     /* Skip CR following LF */
 	while (buffer->len && (buffer->s[0] == '\r')) {
 		buffer->s++;
 		buffer->len--;
 	}
 }
 
 
 static void unix_server_loop(void)
 {
4421b319
 	int ret;
50a0659f
 	str cmd, buffer;
 	static char buf[UNIXSOCK_BUF_SIZE];
 	struct unixsock_cmd* c;
 
 	
 	while(1) {
4421b319
 		reply_addr_len = sizeof(reply_addr);
cf9b82fd
 		ret = recvfrom(rx_sock, buf, UNIXSOCK_BUF_SIZE, 0, 
4421b319
 			       (struct sockaddr*)&reply_addr, &reply_addr_len);
50a0659f
 		if (ret == -1) {
 			LOG(L_ERR, "unix_server_loop: recvfrom: (%d) %s\n", 
 			    errno, strerror(errno));
 			if ((errno == EINTR) || 
 			    (errno == EAGAIN) || 
 			    (errno == EWOULDBLOCK) || 
 			    (errno == ECONNREFUSED)) {
 				DBG("unix_server_loop: Got %d (%s), going on\n",
 				    errno, strerror(errno));
 				continue;
 			}
cf9b82fd
 			LOG(L_CRIT, "BUG: unix_server_loop: unexpected recvfrom error\n");
 			continue;
50a0659f
 		}
 
cf9b82fd
 		buffer.s = buf;
50a0659f
 		buffer.len = ret;
4421b319
 		unixsock_reply_reset();
50a0659f
 
 		if (parse_cmd(&cmd, &buffer) < 0) {
4421b319
 			unixsock_reply_asciiz("400 First line malformed\n");
 			unixsock_reply_send();
50a0659f
 			continue;
 		}
 
 		buffer.s = cmd.s + cmd.len + 1;
 		buffer.len -= cmd.len + 1 + 1;
 		skip_line(&buffer); /* Skip the reply filename */
 
 		c = lookup_cmd(&cmd);
 		if (c == 0) {
 			LOG(L_ERR, "unix_server_loop: Could not find "
 			    "command '%.*s'\n", cmd.len, ZSW(cmd.s));
4421b319
 			unixsock_reply_printf("500 Command %.*s not found\n", cmd.len, ZSW(cmd.s));
 			unixsock_reply_send();
50a0659f
 			continue;
 		}
 
 		ret = c->f(&buffer);
 		if (ret < 0) {
 			LOG(L_ERR, "unix_server_loop: Command '%.*s' failed with "
 			    "return value %d\n", cmd.len, ZSW(cmd.s), ret);
 			     /* Note that we do not send reply here, the 
 			      * function is supposed to do so, it knows the 
 			      * reason of the failure better than us
 			      */
 		}
 	}
 }
 
 
4421b319
 static int get_uptime(void)
 {
 	char* t;
 
 	time(&up_since);
 	t = ctime(&up_since);
 	if (strlen(t) + 1 >= MAX_CTIME_LEN) {
 		LOG(L_ERR, "get_uptime: Too long date %d\n", (int)strlen(t));
 		return -1;
 	}
 	memcpy(up_since_ctime, t, strlen(t) + 1);
 	return 0;
 }
 
 
50a0659f
 /*
b3fef92b
  * Spawn listeners
50a0659f
  */
b3fef92b
 int init_unixsock_children(void)
50a0659f
 {
b3fef92b
 	int i;
50a0659f
 	pid_t pid;
851b529f
 #ifdef USE_TCP
 	int sockfd[2];
 #endif
b3fef92b
 
 	if (!unixsock_name || *unixsock_name == '\0') {
50a0659f
 		return 1;
 	}
 
4421b319
 	if (get_uptime() < 0) {
 		return -1;
 	}
b3fef92b
 	
4421b319
         if (register_core_commands() < 0) {
037024d7
 		close(rx_sock);
 		close(tx_sock);
4421b319
 		return -1;
 	}
 
50a0659f
 	for(i = 0; i < unixsock_children; i++) {
 		process_no++;
851b529f
 #ifdef USE_TCP
 		if(!tcp_disable){
  			if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd)<0){
 				LOG(L_ERR, "ERROR: init_unixsock_server: socketpair"
 						" failed: %s\n", strerror(errno));
 				return -1;
 			}
 		}
 #endif
50a0659f
 		pid = fork();
 		if (pid < 0) {
 			LOG(L_ERR, "init_unixsock_server: Unable to fork: %s\n",
 			    strerror(errno));
037024d7
 			close(rx_sock);
 			close(tx_sock);
50a0659f
 			return -1;
 		} else if (pid == 0) { /* child */
851b529f
 #ifdef USE_TCP
 			if (!tcp_disable){
 				close(sockfd[0]);
 				unix_tcp_sock=sockfd[1];
 			}
 #endif
50a0659f
 			if (init_child(PROC_UNIXSOCK) < 0) {
 				LOG(L_ERR, "init_unixsock_server: Error in "
 				    "init_child\n");
037024d7
 				close(rx_sock);
 				close(tx_sock);
50a0659f
 				return -1;
 			}
 
 			unix_server_loop(); /* Never returns */
 		}
 
 		     /* Parent */
 		pt[process_no].pid = pid;
 		strncpy(pt[process_no].desc, "unix domain socket server", 
 			MAX_PT_DESC);
851b529f
 #ifdef USE_TCP
 		if (!tcp_disable){
 			close(sockfd[1]);
 			pt[process_no].unix_sock=sockfd[0];
 			pt[process_no].idx=-1; /* this is not a "tcp"
 									  process*/
 		}
 #endif
 
50a0659f
 	}
4421b319
 
53c7e0f1
 	DBG("init_unixsock_server: Unix domain socket server successfully initialized @ %s\n",
b3fef92b
 	    unixsock_name);
50a0659f
 	return 1;
 }
 
 
 /*
  * Clean up
  */
 void close_unixsock_server(void)
 {
 	struct unixsock_cmd* c;
037024d7
 	close(rx_sock);
 	close(tx_sock);
50a0659f
 
 	while(cmd_list) {
 		c = cmd_list;
 		cmd_list = cmd_list->next;
 		pkg_free(c);
 	}
 }
 
 
 /*
  * Register a new command
  */
4421b319
 int unixsock_register_cmd(char* command, unixsock_f* f)
50a0659f
 {
4421b319
 	str cmd;
50a0659f
 	struct unixsock_cmd* new_cmd;
 
4421b319
 	cmd.s = command;
 	cmd.len = strlen(command);
 
 	if (lookup_cmd(&cmd)) {
50a0659f
 		LOG(L_ERR, "unixsock_register_cmd: Function already exists\n");
 		return -1;
 	}
 
 	new_cmd = pkg_malloc(sizeof(struct unixsock_cmd));
 	if (new_cmd == 0) {
 		LOG(L_ERR, "register_unixsock_cmd: Out of mem\n");
 		return -1;
 	}
 
4421b319
 	new_cmd->name = cmd;
50a0659f
 	new_cmd->f = f;
 
 	new_cmd->next = cmd_list;
 	cmd_list = new_cmd;
 	
 	DBG("unixsock_register_cmd: New command (%.*s) registered\n", 
4421b319
 	    cmd.len, ZSW(cmd.s));
50a0659f
 	return 1;
 }
 
 
 int unixsock_add_to_reply(const char* buf, size_t len)
 {
 	if (reply_pos.len < len) {
 		LOG(L_ERR, "unixsock_add_to_reply: Buffer too small\n");
 		return -1;
 	}
 
 	memcpy(reply_pos.s, buf, len);
 	reply_pos.s += len;
 	reply_pos.len -= len;
 	return 0;
 }
 
 
 /*
  * Send a reply
  */
4421b319
 ssize_t unixsock_reply_send(void)
50a0659f
 {
6b17cae3
 	return tsend_dgram(tx_sock, 
 			   reply_buf, reply_pos.s - reply_buf,
 			   (struct sockaddr*)&reply_addr, reply_addr_len, 
 			   unixsock_tx_timeout);
50a0659f
 }
 
 
 /*
8b3c4193
  * Send a reply
  */
 ssize_t unixsock_reply_sendto(struct sockaddr_un* to)
 {
 	if (!to) {
 		LOG(L_ERR, "unixsock_reply_sendto: Invalid parameter value\n");
 		return -1;
 	}
 
6b17cae3
 	return tsend_dgram(tx_sock, 
 			   reply_buf, reply_pos.s - reply_buf, 
 			   (struct sockaddr*)to, SUN_LEN(to), 
 			   unixsock_tx_timeout);
8b3c4193
 }
 
 
 /*
50a0659f
  * Read a line, the result will be stored in line
  * parameter, the data is not copied, it's just
  * a pointer to an existing buffer
  */
 int unixsock_read_line(str* line, str* source)
 {
 	if (!line || !source) {
 		LOG(L_ERR, "unixsock_read_line: Invalid parameter value\n");
 		return -1;
 	}
 
 	*line = *source;
 	skip_line(source);
 	line->len = source->s - line->s;
372ed8d2
 	trim_trailing(line);
50a0659f
 	if (line->len) {
 		return 0;
 	} else {
 		return 1;
 	}
 }
 
 
 /*
  * Read body until the closing .CRLF, no CRLF recovery
  * is done so no additional buffer is necessary, body will
  * point to an existing buffer
  */
 int unixsock_read_body(str* body, str* source)
 {
 	int i, state, last_dot;
 
 	enum states {
 		ST_BEGIN,
 		ST_CRLF,
 		ST_DATA,
 		ST_NEWLINE
 	};
 
 	if (!body || !source) {
 		LOG(L_ERR, "unixsock_read_body: Invalid parameter value\n");
 		return -1;
 	}
 
 	if (source->len < 2) {
 		LOG(L_ERR, "unixsock_read_body: Not enough input data "
 		    "(malformed message ?)\n");
 		return -1;
 	}
 
 	state = ST_BEGIN;
 	body->s = source->s;
 	last_dot = 0;
 	for(i = 0; i < source->len; i++) {
 		switch(state) {
 		case ST_BEGIN:
 			if (source->s[i] == '.') {
 				last_dot = i;
 				state = ST_CRLF;
 			} else if (source->s[i] == '\n') {
 				state = ST_NEWLINE;
 			} else {
 				state = ST_DATA;
 			}
 			break;
 			
 		case ST_CRLF:
 			if (source->s[i] == '\n') {
 				body->len = last_dot;
7404c5a9
 				source->s += i + 1;
 				source->len -= i + 1;
50a0659f
 				return 0;
 			} else if (source->s[i] != '\r') {
 				state = ST_DATA;
 			}
 			break;
 
 		case ST_DATA:
 			if (source->s[i] == '\n') {
 				state = ST_NEWLINE;
 			}
 			break;
 
 		case ST_NEWLINE:
 			if (source->s[i] == '.') {
 				last_dot = i;
 				state = ST_CRLF;
 			}
 			break;
 		}
 	}
 
 	LOG(L_ERR, "unixsock_read_body: Could not find the end of the body\n");
 	return -1;
 }
 
 
 /*
  * Read a set of lines, the functions performs CRLF recovery,
  * therefore lineset must point to an additional buffer
  * to which the data will be copied. Initial lineset->len contains
  * the size of the buffer
  */
 int unixsock_read_lineset(str* lineset, str* source)
 {
 	int i, state, len;
 
 	enum states {
 		ST_BEGIN,
 		ST_CRLF,
 		ST_DATA,
 		ST_NEWLINE
 	};
 
 	if (!lineset || !source) {
 		LOG(L_ERR, "unixsock_read_lineset: Invalid parameter value\n");
 		return -1;
 	}
 
86e89f0e
 	if (!lineset->s || !lineset->len) {
 		LOG(L_ERR, "unixsock_read_lineset: Buffer too small\n");
 		return -1;
 	}
 
50a0659f
 	if (source->len < 2) {
 		LOG(L_ERR, "unixsock_read_lineset: Not enough input "
 		    "data (malformed message ?)\n");
 		return -1;
 	}                 
 
 	state = ST_BEGIN;
 	len = 0;
 	for(i = 0; i < source->len; i++) {
 		if (source->s[i] == '\r') {
 			     /* Filter out CR */
 			continue;
 		}
 
 		switch(state) {
 		case ST_BEGIN:
 			if (source->s[i] == '.') {
 				state = ST_CRLF;
 			} else if (source->s[i] == '\n') {
86e89f0e
 				if (len + 2 > lineset->len) goto buf_err;
50a0659f
 				lineset->s[len++] = '\r';
 				lineset->s[len++] = '\n';
 				state = ST_NEWLINE;
 			} else {
86e89f0e
 				if (len + 1 > lineset->len) goto buf_err;
50a0659f
 				lineset->s[len++] = source->s[i];
 			}
 			break;
 			
 		case ST_CRLF:
 			if (source->s[i] == '\n') {
 				lineset->len = len;
7404c5a9
 				source->s += i + 1;
 				source->len -= i + 1;
50a0659f
 				return 0;
 			} else {
86e89f0e
 				if (len + 2 > lineset->len) goto buf_err;
50a0659f
 				lineset->s[len++] = '.';
 				lineset->s[len++] = source->s[i];
 				state = ST_DATA;
 			}
 			break;
 
 		case ST_DATA:
 			if (source->s[i] == '\n') {
86e89f0e
 				if (len + 2 > lineset->len) goto buf_err;
50a0659f
 				lineset->s[len++] = '\r';
 				lineset->s[len++] = '\n';
 				state = ST_NEWLINE;
 			} else {
86e89f0e
 				if (len + 1 > lineset->len) goto buf_err;
50a0659f
 				lineset->s[len++] = source->s[i];
 			}
 			break;
 
 		case ST_NEWLINE:
 			if (source->s[i] == '.') {
 				state = ST_CRLF;
 			} else {
86e89f0e
 				if (len + 1 > lineset->len) goto buf_err;
50a0659f
 				lineset->s[len++] = source->s[i];
 				state = ST_DATA;
 			}
 			break;
 		}
 	}
 
 	LOG(L_ERR, "unixsock_read_body: Could not find the end of the body\n");
 	return -1;
86e89f0e
 
  buf_err:
 	LOG(L_ERR, "unixsock_read_lineset: Buffer too small\n");
 	return -1;
50a0659f
 }
4421b319
 
 
 /*
  * Reset the reply buffer -- start to write
  * at the beginning
  */
 void unixsock_reply_reset(void)
 {
 	reply_pos.s = reply_buf;
 	reply_pos.len = UNIXSOCK_BUF_SIZE;
 }
 
 
 /*
  * Add ASCIIZ to the reply buffer
  */
 int unixsock_reply_asciiz(char* str)
 {
 	int len;
 	
 	if (!str) {
 		LOG(L_ERR, "unixsock_reply_asciiz: Invalid parameter value\n");
 		return -1;
 	}
 
 	len = strlen(str);
 
 	if (reply_pos.len < len) {
 		LOG(L_ERR, "unixsock_reply_asciiz: Buffer too small\n");
 		return -1;
 	}
 
 	memcpy(reply_pos.s, str, len);
 	reply_pos.s += len;
 	reply_pos.len -= len;
 	return 0;
 }
 
 
 /*
  * Add a string represented by str structure
  * to the reply buffer
  */
 int unixsock_reply_str(str* s)
 {
 	if (!s) {
 		LOG(L_ERR, "unixsock_reply_str: Invalid parameter value\n");
 		return -1;
 	}
 
 	if (reply_pos.len < s->len) {
 		LOG(L_ERR, "unixsock_reply_str: Buffer too small\n");
 		return -1;
 	}
 	
 	memcpy(reply_pos.s, s->s, s->len);
 	reply_pos.s += s->len;
 	reply_pos.len -= s->len;
 	return 0;
 }
 
 
 /*
  * Printf-like reply function
  */
 int unixsock_reply_printf(char* fmt, ...)
 {
 	va_list ap;
 	int ret;
 
 	if (!fmt) {
 		LOG(L_ERR, "unixsock_reply_printf: Invalid parameter value\n");
 		return -1;
 	}
 
 	va_start(ap, fmt);
 	ret = vsnprintf(reply_pos.s, reply_pos.len, fmt, ap);
e8d77b2a
 	if ((ret < 0) || (ret >= reply_pos.len)) {
4421b319
 		LOG(L_ERR, "unixsock_reply_printf: Buffer too small\n");
e8d77b2a
 		va_end(ap);
4421b319
 		return -1;
 	}
 
 	va_end(ap);
 	reply_pos.s += ret;
 	reply_pos.len -= ret;
 	return 0;
 }
8b3c4193
 
 
 /*
  * Return the address of the sender
  */
7404c5a9
 struct sockaddr_un* unixsock_sender_addr(void)
8b3c4193
 {
 	return &reply_addr;
 }