/*
 * $Id: extcmd_funcs.c,v 1.15 2004/08/24 08:58:29 janakj Exp $
 *
 *
 * Copyright (C) 2001-2003 FhG Fokus
 *
 * 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
 *
 * History:
 * --------
 * 2003-01-23: switched from t_uac to t_uac_dlg, adapted to new way of
 *             parsing for Content-Type (bogdan)
 * 2003-08-05: adapted to the new parse_content_type_hdr function (bogdan)
 * 2003-09-11: updated to new build_lump_rpl() interface (bogdan)
 * 2003-11-11: build_lump_rpl() removed, add_lump_rpl() has flags (bogdan)
 */


#include <string.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include "extcmd_funcs.h"
#include "clients.h"
#include "../../error.h"
#include "../../str.h"
#include "../../ip_addr.h"
#include "../../data_lump_rpl.h"
#include "../../mem/shm_mem.h"
#include "../../parser/parse_content.h"
#include "../../parser/parse_from.h"
#include "../tm/tm_load.h"


#define NO_CMD   0
#define MSG_CMD  1
#define RPL_CMD  2
#define BYE_CMD  3
#define FTH_CMD  4
#define VOID_CMD 5
#define INV_CMD  6
#define ACK_CMD  7

#define BUFFER_SIZE  2048
#define HEADER_SIZE  5
#define TYPE_LEN     1
#define LEN_LEN      4

#define append_str(_p,_s,_l) \
	{memcpy((_p),(_s),(_l));\
	(_p) += (_l);}


typedef struct anchor_struct
{
	int fd;
	str cmd;
} anchor_t;


/* global variable */
struct tm_binds tmb;
int    rpl_pipe[2];
int    req_pipe[2];



inline int get_int_n2h( char *b, int l)
{
	int n,i;
	for(i=0,n=0;i<l;i++)
		n = (n<<8) + (unsigned char)b[i];
	return n;
}




inline void put_int_h2n( int x, char *b, int l)
{
	int i;
	for(i=0;i<l;i++)
		b[i] = (unsigned char) ((x>>((l-i-1)*8)) & 0x000000FF);
}



#if 0
inline int extcmd_add_contact(struct sip_msg* msg , str* to_uri)
{
	struct lump_rpl *lump;
	char *buf, *p;
	int len;

	len = 9 /*"Contact: "*/ + to_uri->len + 2/*"<>"*/ + CRLF_LEN;

	buf = pkg_malloc( len );
	if(!buf) {
		LOG(L_ERR,"ERROR:extcmd_add_contact: out of memory! \n");
		return -1;
	}

	p = buf;
	append_str( p, "Contact: " , 9);
	*(p++) = '<';
	append_str( p, to_uri->s, to_uri->len);
	*(p++) = '>';
	append_str( p, CRLF, CRLF_LEN);

	lump = build_lump_rpl( buf , len , LUMP_RPL_HDR);
	if(!lump) {
		LOG(L_ERR,"ERROR:extcmd_add_contact: unable to build lump_rpl! \n");
		pkg_free( buf );
		return -1;
	}
	add_lump_rpl( msg , lump );

	pkg_free(buf);
	return 1;
}
#endif



int dump_request(struct sip_msg *msg, char *para1, char *para2)
{
	anchor_t anchor;
	str    body;
	struct to_body  *from;
	char   *cmd;
	int    cmd_len;
	char   *p;
	int    mime;

	/* get the message's body
	 * anyhow we have to call this function, so let's do it at the beginning
	 * to force the parsing of all the headers - like this we avoid separate
	 * calls of parse_headers function for FROM, CONTENT_LENGTH, TO hdrs  */
	body.s = get_body( msg );
	if (body.s==0) {
		LOG(L_ERR,"ERROR:extcmd:dump_msg: cannot extract body from msg!\n");
		goto error;
	}

	/* content-length (if present) must be already parsed */
	if (!msg->content_length) {
		LOG(L_ERR,"ERROR:extcmd:dump_msg: no Content-Length header found!\n");
		goto error;
	}
	body.len = get_content_length( msg );

	/* look for TO header */
	if (!msg->to) {
		LOG(L_ERR,"ERROR:extcmd:dump_msg: no TO header found!\n");
		goto error;
	}

	/* parse the content-type header */
	if ((mime=parse_content_type_hdr(msg))<1 ) {
		LOG(L_ERR,"ERROR:extcmd:dump_msg:cannot parse Content-Type header\n");
		goto error;
	}

	/* check the content-type value */
	if ( mime!=(TYPE_TEXT<<16)+SUBTYPE_PLAIN
	&& mime!=(TYPE_MESSAGE<<16)+SUBTYPE_CPIM ) {
		LOG(L_ERR,"ERROR:extcmd:dump_msg: invalid content-type for a "
			"message request! type found=%d\n",mime);
		goto error;
	}

	if ( parse_from_header(msg)==-1 ) {
		LOG(L_ERR,"ERROR:extcmd:dump_msg: cannot parse FROM header!\n");
		goto error;
	}
	from = (struct to_body*)msg->from->parsed;

#if 0
	/* adds contact header into reply */
	if (extcmd_add_contact(msg,&(get_to(msg)->uri))==-1) {
		LOG(L_ERR,"ERROR:extcmd:dump_msg: can't build contact for reply\n");
		goto error;
	}
#endif

	/*-------------BUILD AND FILL THE COMMAND --------------------*/
	/* computes the amount of memory needed */
	cmd_len = HEADER_SIZE + /* commnad header */
		+ 4 + body.len /*body + size */
		+ 4 + from->uri.len /* from + size */
		+ 4 + get_to(msg)->uri.len /* to + size */
		+ 4 + 0; /* extra_headers + size */
	/* allocs a chunk of shm memory */
	cmd = (char*)shm_malloc( cmd_len );
	if (!cmd) {
		LOG(L_ERR,"ERROR:extcmd:dump_msg: cannot get shm memory!\n");
		goto error;
	}

	/* start filling the commnad (in revert order, from end)*/
	p = cmd + cmd_len;
	/* copy body's body */
	p = p - body.len;
	memcpy( p, body.s, body.len);
	/* put body's lengh */
	p = p-4;
	put_int_h2n( body.len, p, 4);
	/* copy hdr's body - nothing for the moment*/
	/* put hdr's lengh */
	p = p-4;
	put_int_h2n( 0, p, 4);
	/* copy to's body */
	p = p - get_to(msg)->uri.len;
	memcpy( p, get_to(msg)->uri.s, get_to(msg)->uri.len);
	/* put to's lengh */
	p = p-4;
	put_int_h2n( get_to(msg)->uri.len, p, 4);
	/* copy from's body */
	p = p - from->uri.len;
	memcpy( p, from->uri.s, from->uri.len);
	/* put from's lengh */
	p = p-4;
	put_int_h2n( from->uri.len, p, 4);
	/* add the cmd size */
	p = p-4;
	put_int_h2n( cmd_len-HEADER_SIZE, p, 4);
	/* add the cmd type */
	p = p-1;
	put_int_h2n( MSG_CMD, p, 1);


	/* fill in the anchor */
	anchor.fd = 0;
	anchor.cmd.s = cmd;
	anchor.cmd.len = cmd_len;
	/* send the anchor through pipe to the extcmd server process */
	DBG("DEBUG:extcmd:dump_msg: posting request in pipe!\n");
	if (write( req_pipe[1], &anchor, sizeof(anchor))!=sizeof(anchor) ) {
		LOG(L_ERR,"ERROR:extcmd:dump_msg: cannot write to request pipe"
			" : %s\n",strerror(errno) );
		goto error;
	}

	return 1;
error:
	return -1;
}




static int push_reply_to_client(int code, char* reason_s, int reason_l,
																int client_fd)
{
	anchor_t anchor;
	str reply;
	char *p;

	/* compose the reply commnad that will be sent back to the client */
	reply.len = 1/*type*/+4/*cmd_size*/+4/*rpl_code*/+4/*rpl_reason_size*/+
		reason_l/*rpl_reason_string*/;
	reply.s = (char*) shm_malloc( reply.len );
	if (reply.s==0) {
		LOG(L_ERR,"ERROR::extcmd:tuac_callback: no more shm_mem free!\n");
		return -1;
	}
	/* fill up the rpl_cmd - in revert order (from end)*/
	p = reply.s + reply.len;
	/* add reason string */
	p = p-reason_l;
	memcpy( p, reason_s, reason_l);
	/* add reason string len */
	p = p-4;
	put_int_h2n( reason_l, p, 4);
	/* add the rpl code */
	p = p-4;
	put_int_h2n( code, p, 4);
	/* add the cmd size */
	p = p-4;
	put_int_h2n( 4+4+reason_l, p, 4);
	/* add the cmd type */
	p = p-1;
	put_int_h2n( RPL_CMD, p, 1);

	/* push reply on rpl_pipe */
	anchor.fd = client_fd;
	anchor.cmd.s = reply.s;
	anchor.cmd.len = reply.len;
	if (write( rpl_pipe[1] , &anchor, sizeof(anchor) )!=sizeof(anchor)) {
		LOG(L_ERR,"ERROR::extcmd:push_reply_to_client: cannot write "
			"to rpl_pipe : Reason: %s !\n",strerror(errno) );
		return -1;
	}
	return 1;
}




void tuac_callback( struct cell *t, struct sip_msg *msg, int code, void *param)
{
	str  reason;

	DBG("DEBUG:extcmd:tuac_callback: reply status=%d\n", code);
	if(!t->cbp)
	{
		LOG(L_ERR,"ERROR:extcmd:tuac_callback: parameter not received\n");
		return;
	}

	/* get the status text for this reply code */
	get_reply_status( &reason, msg, code);

	push_reply_to_client( code, reason.s, reason.len, *((int*)t->cbp));
	if (reason.s)
		pkg_free(reason.s);
}




int send_sip_req(str* msg_type, str *msg, int client_fd)
{
	char err_buf[256];
	str to;
	str from;
	str hdrs;
	str body;
	int *pcbp;
	char *p;
	char *end;
	int len;
	int ret;
	int err_ret;
	int sip_error;

	/* split the msg into from, to hdrs, body */
	end = msg->s + msg->len;
	p = msg->s;

	/* get from len */
	len = get_int_n2h( p, 4);
	p = p + 4;
	if ( p+len>end) {
		LOG(L_ERR,"ERROR:extcmd:send_sip_req: FROM size to big (%d)\n",len);
		push_reply_to_client(400,"extcmd: invalid FROM header",27,client_fd);
		goto error;
	}
	/* get from body */
	from.s = p;
	from.len = len;
	p += len;
	DBG("DEBUG:extcmd:send_sip_req: from=%d<%.*s>\n",from.len,from.len,from.s);

	/* get to len */
	len = get_int_n2h( p, 4);
	p = p + 4;
	if ( p+len>end) {
		LOG(L_ERR,"ERROR:extcmd:send_sip_req: TO size to big (%d)\n",len);
		push_reply_to_client(400,"extcmd: invalid TO header",25,client_fd);
		goto error;
	}
	/* get to body */
	to.s = p;
	to.len = len;
	p += len;
	DBG("DEBUG:extcmd:send_sip_req: to=%d<%.*s>\n",to.len,to.len,to.s);

	/* get headers len */
	len = get_int_n2h( p, 4);
	p = p + 4;
	if ( p+len>end) {
		LOG(L_ERR,"ERROR:extcmd:send_sip_req: HDRS size to big (%d)\n",len);
		push_reply_to_client(400,"extcmd: invalid HEADERS field",29,client_fd);
		goto error;
	}
	/* get headers body */
	hdrs.s = p;
	hdrs.len = len;
	p += len;
	DBG("DEBUG:extcmd:send_sip_req: hdrs=%d<%.*s>\n",hdrs.len,hdrs.len,hdrs.s);

	/* get body len */
	len = get_int_n2h( p, 4);
	p = p + 4;
	if ( p+len>end) {
		LOG(L_ERR,"ERROR:extcmd:send_sip_req: BODY size to big (%d)\n",len);
		push_reply_to_client(400,"extcmd: invalid BODY field",26,client_fd);
		goto error;
	}
	/* get body's body */
	body.s = p;
	body.len = len;
	DBG("DEBUG:extcmd:send_sip_req: body=%d<%.*s>\n",body.len,body.len,body.s);

	/* allocate the param in shm */
	if( !(pcbp = (int*)shm_malloc(sizeof(int*))) ) {
		LOG(L_ERR,"ERROR:extcmd:send_sip_req: no more shm mem free!!\n");
		push_reply_to_client(500,"extcmd: no shm memory free",26,client_fd);
		goto error;
	}
	*pcbp = client_fd;

	/* send the message */
	ret = tmb.t_request(  msg_type,   /* request type */
			0,                      /* Request-URI */
			&to,                      /* To */
			&from,                    /* From */
			&hdrs,                    /* Additional headers including CRLF */
			&body,                    /* Message body */
			tuac_callback,           /* Callback function */
			(void*)pcbp               /* Callback parameter */
		);
	if (ret<=0) {
		err_ret=err2reason_phrase(ret, &sip_error, err_buf,
				sizeof(err_buf), "EXTCMD" ) ;
		if (err_ret > 0 )
		{
			push_reply_to_client( sip_error, err_buf, err_ret, client_fd);
		} else {
			push_reply_to_client(500,"EXTCMD unknown error\n",20,client_fd);
		}
	}
	return 1;

error:
	return -1;
}




inline int forward_reply_to_client(int pipe_fd)
{
	anchor_t anchor;

	anchor.cmd.s = 0;
	anchor.cmd.len = 0;

	/* get cmd from pipe */
	if (read(pipe_fd,&anchor,sizeof(anchor))!=sizeof(anchor) ) {
		LOG(L_ERR,"ERROR:extcmd:send_cmd_from_pipe_to_client: cannot read from"
			" pipe: %s \n", strerror(errno));
		goto error;
	}

	/* send the reply */
	if (write( anchor.fd, anchor.cmd.s, anchor.cmd.len)!=anchor.cmd.len ) {
		LOG(L_ERR,"ERROR:extcmd:send_cmd_from_pipe_to_client: cannot write to "
			"socket: %s \n", strerror(errno));
		goto error;
	}

	/* free the mem */
	shm_free( anchor.cmd.s );

	return 1;
error:
	if (anchor.cmd.s)
		shm_free( anchor.cmd.s );
	return -1;
}




static int forward_request_to_all_clients(int pipe_fd)
{
	anchor_t anchor;
	client_t *client;
	int fd;
	int i;

	anchor.cmd.s = 0;
	anchor.cmd.len = 0;

	/* get request cmd from pipe */
	if (read(pipe_fd,&anchor,sizeof(anchor))!=sizeof(anchor) ) {
		LOG(L_ERR,"ERROR:extcmd:send_cmd_from_pipe_to_client: cannot read from"
			" pipe: %s \n", strerror(errno));
		goto error;
	}

	/* send the reply to all clients */
	for(i=0; i<get_nr_clients(); i++) {
		client = get_client( i );
		fd = client->fd;
		if (write( fd, anchor.cmd.s, anchor.cmd.len)!=anchor.cmd.len ) {
			LOG(L_ERR,"ERROR:extcmd:forward_request_to_all_client: cannot "
				"write to socket: %s \n", strerror(errno));
			continue;
		}
	}

	/* free the mem */
	shm_free( anchor.cmd.s );

	return 1;
error:
	if (anchor.cmd.s)
		shm_free( anchor.cmd.s );
	return -1;
}




inline int read_n( int fd, int n, char *b)
{
	int l;
	int c;

	l=0;
	c=0;
	while( l<n && (c=read(fd,b+l,n))>0 )
		l += c;
	if (c<0)
		return c;
	return l;
}




static int get_cmd( int fd, str *cmd)
{
	static char buffer[BUFFER_SIZE];
	int cmd_type;
	int cmd_len;
	int  len;

	/* get command header */
	len = read_n( fd, HEADER_SIZE , buffer);
	if (len<HEADER_SIZE) {
		LOG(L_ERR,"ERROR:extcmd:get_cmd: cannot read command's header: %s \n",
			len<0?strerror(len):"incomplete string");
		return NO_CMD;
	}

	/* decode command header */
	cmd_type = get_int_n2h( buffer, TYPE_LEN);
	cmd_len = get_int_n2h( buffer+TYPE_LEN, LEN_LEN);
	DBG("DEBUG:extcmd:get_cmd: type=%d, len=%d \n",cmd_type, cmd_len);

	/* read the command body */
	len = read_n( fd, cmd_len , buffer);
	if (len<cmd_len) {
		LOG(L_ERR,"ERROR:extcmd:get_cmd: cannot read command's body: %s \n",
			len<0?strerror(len):"incomplete string");
		return NO_CMD;
	}
	DBG("DEBUG:extcmd:get_cmd: command = [%.*s]\n",cmd_len,buffer);

	/* return the command body */
	cmd->s = buffer;
	cmd->len = cmd_len;

	return cmd_type;
}



#if 0
static int send_void_command( int sock_fd )
{
	char buf[HEADER_SIZE];

	/* add command type */
	put_int_h2n( VOID_CMD, buf, 1);
	/* add the cmd len */
	put_int_h2n( 0, buf+1, 4);
	/* send the reply */
	if (write( sock_fd, buf, HEADER_SIZE)!=HEADER_SIZE ) {
		LOG(L_ERR,"ERROR:extcmd:send_void_commnad: cannot write to "
			"socket: %s \n", strerror(errno));
		return -1;
	}
	return 1;
}
#endif




inline void FD_SET_AND_MAX( fd_set *fdset, int fd, int *max_fd)
{
	FD_SET( fd, fdset );
	if (fd>*max_fd)
		*max_fd = fd;
}




inline void FD_CLR_AND_MAX( fd_set *fdset, int fd, int *max_fd)
{
	int i;
	int new_max = 0;

	FD_CLR( fd, fdset );
	if (fd==*max_fd) {
		for(i=0;i<=*max_fd;i++)
			if ( FD_ISSET(i,fdset) && i>new_max )
				new_max = i;
		*max_fd = new_max;
	}
}




void extcmd_server_process( int server_sock )
{
	str message_req = { "MESSAGE", 7};
	//str invite_req = { "INVITE", 6};
	fd_set read_set;
	fd_set wait_set;
	client_t *client;
	union sockaddr_union sau;
	int sau_len;
	str cmd;
	int cmd_type;
	int max_fd;
	int fd = -1;
	int index;

	/* set SIG_PIPE to be ignored by this process */
	if (signal( SIGPIPE, SIG_IGN)==SIG_ERR) {
		LOG(L_ERR,"ERROR:extcmd_server_process: cannot set SIGPIPE to be "
			"ignored! \n");
		goto error;
	}

	/* set the socket for listening */
	if ( listen( server_sock, 10)==-1 ) {
		LOG(L_ERR,"ERROR:extcmd_server_process: cannot listen to the server"
			" socket: %s \n", strerror(errno) );
		goto error;
	}

	sau_len = sizeof( union sockaddr_union );
	/* prepare fd set for select */
	FD_ZERO( &wait_set );
	max_fd = 0;
	FD_SET_AND_MAX( &wait_set, server_sock, &max_fd);
	FD_SET_AND_MAX( &wait_set, req_pipe[0], &max_fd);
	FD_SET_AND_MAX( &wait_set, rpl_pipe[0], &max_fd);

	while(1) {
		/* what fd should we listen to? */
		read_set = wait_set;
		if (clients_is_full())
			FD_CLR_AND_MAX( &read_set, server_sock, &max_fd);
		/* wait for read something */
		DBG("DEBUG:extcmd_server_process: waiting to read!\n");
		if (select( max_fd+1, &read_set, 0, 0, 0)==-1) {
			LOG(L_ERR,"ERROR:extcmd_server_process: select failed : %s\n",
				strerror(errno) );
			sleep(1);
			continue;
		}

		/* let's see what we read */
		/* maybe is a connect request !?*/
		if ( FD_ISSET(server_sock, &read_set) ) {
			fd=accept(server_sock,(struct sockaddr*)&sau,(socklen_t*)&sau_len);
			if (fd==-1) {
				LOG(L_ERR,"ERROR: extcmd_server_process: accept failed : %s\n",
					strerror(errno) );
			} else {
				DBG("DEBUG:extcmd_server_process: connection accepted \n");
				add_client(fd);
				/* add this socket to wait_sock to read from it */
				FD_SET_AND_MAX( &wait_set, fd, &max_fd);
			}
		}

		/* maybe is a from req_pipe ?! */
		if ( FD_ISSET(req_pipe[0], &read_set) ) {
			forward_request_to_all_clients( req_pipe[0] );
		}

		/* maybe is a SIP reply from rpl_pipe ?! */
		if ( FD_ISSET(rpl_pipe[0], &read_set) ) {
			forward_reply_to_client( rpl_pipe[0] );
		}

		/* maybe is from a client -> look for it's fd */
		for( fd=-1,index=0 ; index<get_nr_clients() ; index++ ) {
			client = get_client(index);
			if ( !client || !FD_ISSET(client->fd,&read_set) )
				continue;
			/* we can read something from this client */
			fd = client->fd;
			/* get the command from the client */
			cmd_type = get_cmd(fd,&cmd);
			switch (cmd_type) {
				/* NO commnad - nothing was read - probably disconnection */
				case NO_CMD :
				/* BYE command - used by the client to end the connection */
				case BYE_CMD:
					DBG("DEBUG:extcmd_server_process: BYE received->close\n");
					/* remove client's fd from wait_sock set */
					FD_CLR_AND_MAX( &wait_set, fd, &max_fd);
					/* remove client */
					del_client(index);
					index--;
					/* close the connection */
					close(fd);
					break;
				/* MSG commnad - client wants to send a SIP message request */
				case MSG_CMD:
					DBG("DEBUG:extcmd_server_process: MSG received\n");
					/* send the message */
					send_sip_req( &message_req, &cmd, fd);
					break;
				/* unknown commnad was received - ignore it */
				default:
					LOG(L_ERR,"ERROR:extcmd_server_process: unknown command "
					"[%d] received from client (fd=%d)\n",cmd_type,fd);
			} /* end switch */
		} /* end for */
	}

error:
	return;
}



syntax highlighted by Code2HTML, v. 0.9.1