/*
 * ImapProxy - a caching IMAP proxy daemon
 * Copyright (C) 2002 Steven Van Acker
 * 
 * This program 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.
 * 
 * This program 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.
 *
 */


#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include <stdarg.h>
#include <ctype.h>
#include <signal.h>

#include <output.h>
#include <network.h>
#include <base64.h>
#include <database.h>
#include <splitstring.h>
#include <configfile.h>
#include <processing_child.h>
#include <defines.h>
#include <misc.h>


DB_record *forkedchild = NULL;

int idle = 0;

/* this function is used to send back an IPC message to the parent
 * it will send a code, that can be found in the headers
 */
int child_send_ipc_returncode(DB_record *p,int code)
{
    write(p->ipcfd[1],&code,sizeof(int));
    idle = 1;
    return 0;
}

/*
 * This function handles a closed connection from the client side.
 * Whatever the current command to the server is, don't send it
 */
int child_handle_client_connection_closed(DB_record *p)
{
    debug("[CHILD CLIENT CLOSED] <%d>\n",p->clientsocket);

    /* do nothing except report this to the parent ! */

    child_send_ipc_returncode(p,DB_IPC_RETURNCODE_CLIENT_CLOSED);
    return 0;
}

/* This function handles errors in the client connection.
 */

int child_handle_client_connection_error(DB_record *p)
{
    debug("child_handle_client_connection_error(): read() error on socket %d (%m)\n",p->clientsocket);
   
    /* same as child_handle_client_connection_closed */
   
    child_send_ipc_returncode(p,DB_IPC_RETURNCODE_CLIENT_ERROR);
    return 0;
}

#define INDEX_FOR_SERVER        0
#define INDEX_FOR_CLIENT        1

/* this function handles a complete message.
 * in the child, we only need to process LOGOUT messages
 */
int child_process_client_message(DB_record *p)
{
    struct message_t *currmessage = NULL;
    int i = 0;
    int argcount = 0;
    char *tmp = NULL;
    Split_string ss;
    
    currmessage = &(p->clientmessage);
    
    if(!currmessage->count || !db_get_client_message(p,0))
	return 0;
	
    for(i = 0;i < currmessage->count; i++)			    
    {
	if((tmp = db_get_client_message(p,i)))
	{
	    if(!i)
		debug("[CC:] %.80s%s",tmp,strlen(tmp)>80?"...\n":"");
	    else
		debug("[CC:] ...\n");
	}
    }

    init_split_string(&ss);
    make_split_string_strip_crlf(db_get_client_message(p,0),&ss," ");

    /* since we are already authenticated, there are no continuation requests, and every message should have at least 2 words */
    if((argcount = split_string_count_parts(&ss)) < 2)
	goto c_process_error;

    tmp = split_string_get(&ss,1);
    
    if(!strcasecmp(tmp,"LOGOUT"))
    {
	if(argcount != 2)
	{
	    debug("child_process_client_message(): LOGOUT attempted with invalid amount of arguments (argcount = %d).\n",argcount);
	    goto c_process_error;
	}

	debug("[CHILD CLIENT PROCESS] LOGOUT received\n");

	socket_printf(p->clientsocket,"* BYE " PROGNAME " " VERSION " closing connection.\r\n");
	socket_printf(p->clientsocket,"%s OK LOGOUT completed\r\n",split_string_get(&ss,0));

	/* instead of logging out, keep the connection open
	 * and open inbox readonly
	 * that way there is mess with mailbox locks
	 */

	/* FIXME: This is dirty. We don't catch the return of the server, 
	 * so the next client could possibly receive the replies
	 */
	socket_printf(p->serversocket,"1234 EXAMINE INBOX\r\n");
	
	/* this is a normal exit.
	 * the client will wait for the server to close the connection
	 * but the server doesn't know the client is logging out
	 * so, the task is up to the parent to close the client connection
	 */
	child_send_ipc_returncode(p,DB_IPC_RETURNCODE_NORMAL);
	
	goto c_real_end_process;
    }
	    
//c_end_process:
    
    for(i = 0;i < currmessage->count; i++)
	if((tmp = db_get_client_message(p,i)))
	    write(p->serversocket, tmp, strlen(tmp));
    
c_real_end_process: 
    delete_split_string(&ss);
    return 0;

c_process_error:

    for(i = 0;i < currmessage->count; i++)
	if((tmp = db_get_client_message(p,i)))
	    write(p->serversocket, tmp, strlen(tmp));

//c_real_process_error:
    delete_split_string(&ss);
    debug("child error occured\n");
    return -1;
}

/* handle messages from the server
 */
int child_process_server_message(DB_record *p)
{
    struct message_t *currmessage = NULL;
    int i = 0;
    int argcount = 0;
    char *tmp = NULL;
    Split_string ss;
    
    currmessage = &(p->servermessage);
   
    /* print to screen */
    for(i = 0;i < currmessage->count; i++)
    {
	if((tmp = db_get_server_message(p,i)))
	{
	    if(!i)
		debug("[CS:] %.80s%s",tmp,strlen(tmp)>80?"...\n":"");
	    else
		debug("[CS:] ...\n");
	}
    }

    /* split the message */
    init_split_string(&ss);
    make_split_string_strip_crlf(db_get_server_message(p,0),&ss," ");

    if((argcount = split_string_count_parts(&ss)) < 1)
	goto s_process_error;
   
    if(!strcmp(split_string_get(&ss,0),"*"))
    {
	
	if(!strcasecmp(split_string_get(&ss,1),"BYE"))
	{
	    debug("child_process_server_message(): Server sent \"* BYE ...\"\n");
	    child_send_ipc_returncode(p,DB_IPC_RETURNCODE_SERVER_CLOSED);
	}
	
	goto s_end_process;
    }
    
s_end_process:

    for(i = 0;i < currmessage->count; i++)
        if((tmp = db_get_server_message(p,i)))
            write(p->clientsocket, tmp, strlen(tmp));

    delete_split_string(&ss);
    return 0;

s_process_error:

    for(i = 0;i < currmessage->count; i++)
        if((tmp = db_get_server_message(p,i)))
            write(p->clientsocket, tmp, strlen(tmp));

    delete_split_string(&ss);
    debug("child error occured\n");
    return -1;
}

/* this function handles new data from a connection
 * the data is parsed into a message here
 */
int child_handle_connection_new_data(DB_record *p,char *buffer,int len,int what)
{
    struct message_t *currmessage = NULL;
    char *tmpbuf = NULL, *tmpp = NULL;
    char *buf = NULL;
    int tmpi = 0;
    int n = 0;
    
    /* new data, store it in buffer 
     * handle the data if it is complete (contains \r\n)
     */

    if(db_get_server_connection_state(p) != DB_STATE_SERVER_CONNECTED)
    {
	debug("child_handle_connection_new_data(): Connection to server is not initialised yet (this should not happen) !!!\n");
	child_handle_client_connection_error(p);

	return -1;
    }
    
    if(what == INDEX_FOR_SERVER)
	currmessage = &(p->servermessage);
    else
	currmessage = &(p->clientmessage);
    
    buf = buffer;
    
    while(len > 0 && !idle)
    {
	if(currmessage->literalcountdown > 0)
	{
	    /* we are in literal mode
	     * meaning : append everything to the message, untill the literalcountdown is 0
	     */
	    if(currmessage->literalcountdown < len)
	    {
		n = currmessage->literalcountdown;
		if(!(tmpbuf = (char *)calloc(n+1,sizeof(char))))
		{
		    debug("child_handle_connection_new_data(): Out of memory !\n");
		    return -1;
		}

		strncpy(tmpbuf,buf,n);

		if(what == INDEX_FOR_SERVER)
		    db_append_to_server_message(p,tmpbuf);
		else
		    db_append_to_client_message(p,tmpbuf);
		
		free(tmpbuf);
		tmpbuf = NULL;
	    }
	    else 
	    {
		n = len;
		if(what == INDEX_FOR_SERVER)
		    db_append_to_server_message(p,buf);
		else
		    db_append_to_client_message(p,buf);
	    }

	    /* change literal countdown */
	    currmessage->literalcountdown -= n;

	    /* update pointer in current buffer */
	    buf += n;
	    len -= n;

	}
	else
	{
	    /* we are in normal mode
	     * meaning: whatever lies ahead, process it normally
	     */

	    /* find \r\n */
	    tmpp = strstr(buf,"\r\n");

	    if(tmpp)
	    {
		/* a command ends in this line
		 *
		 * there are 2 options:
		 * 	* the last word is a {123} literal header -> a literal starts and the message doesn't end
		 * 	* no {123} -> the command is complete, and the message ends
		 */

		/* append to the message */
		n = tmpp - buf + strlen("\r\n");
		
		if(!(tmpbuf = (char *)calloc(n+1,sizeof(char))))
		{
		    debug("child_handle_connection_new_data(): Out of memory !\n");
		    return -1;
		}
		
		strncpy(tmpbuf,buf,n);

		if(what == INDEX_FOR_SERVER)
		    db_append_to_server_message(p,tmpbuf);
		else
		    db_append_to_client_message(p,tmpbuf);

		/* now that it is appended, check for {123} */

		if((tmpi = check_for_literal_count(tmpbuf)) < 0)
		{
		    /* no literal count -> process and delete message */
		    if(what == INDEX_FOR_SERVER)
		    {
			if(p->servermessage.status != DB_MESSAGE_PASSTHROUGH)
			    child_process_server_message(p);
			
			db_delete_server_message(p);
		    }
		    else
		    {
			if(p->clientmessage.status != DB_MESSAGE_PASSTHROUGH)
			    child_process_client_message(p);
			
			db_delete_client_message(p);
		    }
		}
		else
		{
		    /* start of literal -> set all vars, and jump back in the loop */
		    currmessage->literalcountdown = tmpi;
		    if(what == INDEX_FOR_SERVER)
			db_create_new_line_in_server_message(p);
		    else
			db_create_new_line_in_client_message(p);
		}

		free(tmpbuf);
		tmpbuf = NULL;
		len -= n;
		buf += n;
		
	    }
	    else
	    {
		/* no \r\n
		 * just plain old regular data, append to the message
		 */

		if(what == INDEX_FOR_SERVER)
		    db_append_to_server_message(p,buf);
		else
		    db_append_to_client_message(p,buf);

		len -= strlen(buf);
		buf += strlen(buf);
	    }
	}
    }

    return 0;
}

int child_handle_client_connection_new_data(DB_record *p,char *buffer,int len)
{
    return child_handle_connection_new_data(p,buffer,len,INDEX_FOR_CLIENT);
}

int child_handle_server_connection_closed(DB_record *p)
{
    debug("[CHILD SERVER CLOSED] <%d>\n",p->serversocket);

    child_send_ipc_returncode(p,DB_IPC_RETURNCODE_SERVER_CLOSED);
    return 0;
}

int child_handle_server_connection_error(DB_record *p)
{
    debug("child_handle_server_connection_error(): read() error on socket %d (%m)\n",p->serversocket);
    
    child_send_ipc_returncode(p,DB_IPC_RETURNCODE_SERVER_ERROR);
    return 0;
}

int child_handle_server_connection_new_data(DB_record *p,char *buffer,int len)
{
    return child_handle_connection_new_data(p,buffer,len,INDEX_FOR_SERVER);
}

/* this function checks if the server or client sent anything, and processes it */
int child_conversate()
{
    char buffer[1024];
    fd_set readset;
    struct timeval tv;

    int r = 0, rr = 0, maxfd = 0;
    
    bzero(buffer,sizeof(buffer));
    FD_ZERO(&readset);

#define SELECT_TIMEOUT_VALUE 60
    tv.tv_sec = SELECT_TIMEOUT_VALUE;
    tv.tv_usec = 0;
	    
    /* check if we are a forked process ... */

    if(forkedchild && !idle)
    {
	maxfd = forkedchild->serversocket;
	if(maxfd < forkedchild->clientsocket) 
	    maxfd = forkedchild->clientsocket;

	maxfd++;

	FD_SET(forkedchild->serversocket,&readset);
	FD_SET(forkedchild->clientsocket,&readset);
	
	if((r = select(maxfd,&readset,NULL,NULL,&tv)) > 0)
	{
            if((db_get_server_connection_state(forkedchild) == DB_STATE_SERVER_CONNECTED) && FD_ISSET(forkedchild->clientsocket,&readset))
            {
                rr = read(forkedchild->clientsocket,buffer,sizeof(buffer)-1);

                if(rr < 0)
                {
                    child_handle_client_connection_error(forkedchild);
                }
                else if(rr == 0)
                {
                    child_handle_client_connection_closed(forkedchild);
                }
                else
                {
                    buffer[rr] = '\0';
                    child_handle_client_connection_new_data(forkedchild,buffer,rr);
                }
            }
            else if(FD_ISSET(forkedchild->serversocket,&readset))
            {
                rr = read(forkedchild->serversocket,buffer,sizeof(buffer)-1);

                if(rr < 0)
                {
                    child_handle_server_connection_error(forkedchild);
                }
                else if(rr == 0)
                {
                    child_handle_server_connection_closed(forkedchild);
                }
                else
                {
                    buffer[rr] = '\0';
                    child_handle_server_connection_new_data(forkedchild,buffer,rr);
                }
            }	    
	}
	else if(!r)
	{
	    debug("child_conversate(): select() timed out. (%d seconds without activity)\n",SELECT_TIMEOUT_VALUE);
	    return 1;
	}
	else
	{
	    if(errno != EINTR)
	    {
	    	debug("child_conversate(): select() error (%m)\n");
	    	return -1;
	    }
	}

	return 0;
    }
    else if(idle)
    {
	/* if we get here, it means the child has done processing data, and set forkedchild to NULL
	 * now, we have to wait untill the parent processes the ipc returncode, and kills this child.
	 */
	while(1)
	{
	    debug("child %d in waitcycle\n",getpid());
	    sleep(10);
	}
    }

    return -1;
}




syntax highlighted by Code2HTML, v. 0.9.1