/*
 * 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 <sys/wait.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 <time.h>

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

extern int Globaltime;

/* this function checks if the server allows authenticate command.
 * it checks the given split string for AUTH=LOGIN
 */
int parent_can_use_authenticate_login(Parsed_message *pm)
{
    int i,n;
  
    return 0;

    n = parsed_message_count_args(pm);
   
    /* start at 2, because the string starts with * and CAPABILITY, so we can skip those
     */
    for(i = 2;i < n; i++)
    {
	if(!strcasecmp(parsed_message_get_arg(pm,i),"AUTH=LOGIN"))
	{
	    debug("parent_can_use_authenticate_login(): AUTHENTICATE LOGIN is allowed !! using it...\n");
	    return 1;
	}
    }

    debug("parent_can_use_authenticate_login(): AUTHENTICATE LOGIN is not allowed !! Falling back on LOGIN instead.\n");
    return 0;
}

/* this function sets up a connection to the server
 * and sets appropriate flags
 */
int parent_init_server_connection(DB_record *p)
{
	struct sockaddr_in remote,local;
	int size = 0;
	
	if((p->serversocket = nonblock_socket()) < 0)
        {
	    debug("parent_init_server_connection(): Couldn't not open a non-blocking socket for serverconnection !\n");
	    return -1;
        }
    
        bzero(&remote,sizeof(remote));
	remote.sin_family = PF_INET;
        remote.sin_port = htons(config_remote_port);
        remote.sin_addr.s_addr  = config_remote_address;
    	    
        if(connect(p->serversocket,(const struct sockaddr *)&remote,sizeof(remote)))
        {
	    if(errno != EINPROGRESS)
	    {
		debug("parent_init_server_connection(): Couldn't connect to remote (%m)\n");
		return -1;
	    }
        }
	
	size = sizeof(local); 
	
	if ((getsockname(p->serversocket,(struct sockaddr *) &local, &size ) < 0))
	{
	    debug("parent_init_server_connection(): getsockname error ! (%s)\n",strerror(errno));
	    return -1;
	}

	p->proxylocalport = ntohs(local.sin_port);
	      
        db_set_server_connection_state(p,DB_STATE_SERVER_CONNECTION_STARTED);
	return 0;
}

/* fork record and set flags */
int parent_fork_record(DB_record *p,int mainfd)
{
    DB_record *x = NULL,*y = NULL;
    
    pid_t pid;

    if(pipe(p->ipcfd))
    {
	debug("parent_conversate(): Couldn't create ipc pipe %m\n");
	return -1;
    }
	
    pid = fork();

    if(pid < 0)
    {
	debug("parent_conversate(): Couldn't fork ! (%m)\n");
	return -1;
    }
    else if(pid == 0)
    {
	/* child process */

	/* we don't need to listening port */
	close(mainfd);

	/* we don't need the other records either */

	x = DB_start;

	while(x)
	{
	    y = x->next;

	    if(x != p)
	    {
		/* remove all except ourselves */
		db_del(x);
	    }

	    x = y;
	}
	
	db_set_record_state(p,DB_STATE_RECORD_FORKED_CHILD);
	
	forkedchild = p;
	/* reset the loop */
	return 0;
    }
    else /* pid > 0 */
    {
	/* parent process */
	db_set_record_state(p,DB_STATE_RECORD_ACTIVE);
	p->pid = pid;
	debug("Forked a child %d\n",pid);

	/* set nonblocking */
	if(fcntl(p->ipcfd[0],F_SETFL,O_NONBLOCK)<0)
	{
	    debug("parent_conversate(): Couldn't set O_NONBLOCK (%m)\n");
	    return -1;
	}
	    
	forkedchild = NULL;

	/* we don't need the client anymore */
	close(p->clientsocket);
	p->clientsocket = -1;

	return pid;
    }

    return -1;
}

/* return 0 on success
 */
int parent_locate_and_activate_record(DB_record *p)
{
    DB_record *r = NULL;

    if(!(r = db_find_record(p,p->username,p->password)))
    {
	debug("parent_locate_and_activate_record(): No free record found for \"%s\"\n",p->username);
	return -1;
    }

    /* we found an inactive record for this user.
     *
     * instead of reactivating it, we will copy the necessary data to the temporary record
     * and remove the inactive one.
     */

    debug("parent_locate_and_activate_record(): Free record found for \"%s\". Reactivating ...\n",p->username);
    
    p->serversocket = r->serversocket;
    r->serversocket = -1;

    /* remember the local port */
    p->proxylocalport = r->proxylocalport;
   
    p->reuse = r->reuse + 1;

    /* we are ready to fork now */
    db_set_record_state(p,DB_STATE_RECORD_READY_TO_FORK);

    /* the server is connected */
    db_set_server_connection_state(p,DB_STATE_SERVER_CONNECTED);
    
    /* the server has authenticated us */
    socket_printf(p->clientsocket,"%s OK Reactivating record\r\n",p->clientauthtag);
    log(LOG_NOTICE,"%s Reactivating record for user \"%s\" (%d).\n",db_get_record_info(p),p->username,p->reuse);
    
    db_set_server_state(p,DB_STATE_SERVER_SENT_LOGIN_OK);
    
    /* the client is authenticated */
    db_set_client_state(p,DB_STATE_CLIENT_AUTHENTICATED);

    /* the proxy passed the LOGIN OK message to the client */
    db_set_proxy_state(p,DB_STATE_PROXY_SENT_LOGIN_OK_TO_CLIENT);

    db_set_misc_state(p,DB_STATE_MISC_NONE);

    /* remove the old record though */
    db_set_misc_state(r,DB_STATE_MISC_REMOVE_RECORD);
   
    
    
    return 0;
}

/*
 * This function handles a new incoming connection (connecting to the proxy)
 * It should :
 * 	check if the connection is allowed and close it if necessary
 * 	create a new database record and initialise it.
 */
int parent_handle_new_connection(int fd)
{
    int newfd;
    DB_record rec;
    struct sockaddr_in addr; 
    int addrlen = sizeof(struct sockaddr);
    
    /* new connection arrived, add to database */
    newfd = accept(fd,(struct sockaddr *)&addr,&addrlen);
    
    if(newfd < 0)
    {
	debug("parent_handle_new_connection(): accept returned error: %m\n");
	return -1;
    }
    
    log(LOG_NOTICE,"Connection received from %s:%d.\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
    
    if(check_if_blocked(addr.sin_addr.s_addr))
    {
	/* ip is blocked ! */
	debug("[PARENT CLIENT %s:%d REFUSED]\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
	log(LOG_NOTICE,"Connection refused from %s:%d.\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
	socket_printf(newfd,"* BYE Access to proxy denied.\r\n");
	close(newfd);
	return 0;
    }
    
    
    db_init_record(&rec);
    rec.clientsocket = newfd;

    rec.clientlocaladdress.s_addr = addr.sin_addr.s_addr;
    rec.clientlocalport = ntohs(addr.sin_port);
    
    /* record will be temporary untill enough data is gathered to authenticate (or reject) it
     */
    db_set_record_state(&rec,DB_STATE_RECORD_TEMPORARY);

    socket_printf(newfd,"* OK IMAP4rev1 "PROGNAME" "VERSION" Welcome to my dominion.\r\n");
    db_set_proxy_state(&rec,DB_STATE_PROXY_SENT_GREETING_TO_CLIENT);
   
    db_add(&rec);

    debug("[PARENT CLIENT CONNECT] <%d>\n",rec.clientsocket);

    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 parent_handle_client_connection_closed(DB_record *p)
{
    debug("[PARENT CLIENT CLOSED] <%d>\n",p->clientsocket);

    /* if a client is connected, we are not in DB_STATE_RECORD_INACTIVE mode
     * DB_STATE_RECORD_ACTIVE is not possible (otherwise we would be forked)
     * so, there is no server to send anything to.
     */
    db_set_misc_state(p,DB_STATE_MISC_REMOVE_RECORD);
    return 0;
}

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

int parent_handle_client_connection_error(DB_record *p)
{
    debug("parent_handle_client_connection_error(): read() error on socket %d (%m)\n",p->clientsocket);
    
    db_set_misc_state(p,DB_STATE_MISC_REMOVE_RECORD);
    return 0;
}

#define INDEX_FOR_SERVER        0
#define INDEX_FOR_CLIENT        1

/* process message coming from the client */
int parent_process_client_message(DB_record *p)
{
    struct message_t *currmessage = NULL;
    int i = 0;
    int argcount = 0;
    char *tmp = NULL;
    Parsed_message pm;
    
    currmessage = &(p->clientmessage);
    
    if(!currmessage->count || !db_get_client_message(p,0))
    {
	debug("empty message ?!?! how does this happen ??\n");
	return 0;
    }

    if(db_get_client_state(p) == DB_STATE_CLIENT_AUTHENTICATED)
    {
	debug("parent_process_client_message(): We should not be in this state !!! Authenticated clients should fork.\n");
    }
   
    /* output some things for debug */

    if(config_enable_debug)
    {
	for(i = 0;i < currmessage->count; i++)			    
	{
	    if((tmp = db_get_client_message(p,i)))
	    {
		if(!i)
		    debug("[PC:] %.80s%s",tmp,strlen(tmp)>80?"...\n":"");
		else
		    debug("[PC:] ...\n");
	    }
	}
    }

    /* parse the message */
    init_parsed_message(&pm);
    make_parsed_message(currmessage,&pm);
    
    /* some debug */
    print_parsed_message(&pm);

    /* continuation of the authenicate command
     * if user sends "*", then he/she wants to abort !
     */
    if(db_get_client_state(p) == DB_STATE_CLIENT_SENT_AUTHENTICATE_COMMAND && db_get_proxy_state(p) == DB_STATE_PROXY_SENT_USERNAME_REPLY_TO_CLIENT)
    {
	char username[DB_STRING_LENGTH+1];

	tmp = parsed_message_get_idtag(&pm); 

	if(tmp)
	{
	    if(!strcmp(tmp,"*"))
	    {
		log(LOG_NOTICE,"%s User aborted login.\n",db_get_record_info(p));
		socket_printf(p->clientsocket,"%s BAD AUTHENTICATE LOGIN cancelled\r\n",p->clientauthtag);
		db_set_proxy_state(p,DB_STATE_PROXY_SENT_GREETING_TO_CLIENT);
		db_set_client_state(p,DB_STATE_CLIENT_NOT_AUTHENTICATED);
		free(p->clientauthtag);
		p->clientauthtag = NULL;
		goto c_real_end_process;
	    }
	    else
	    {
		base64decode(username,tmp,strlen(tmp));
		strncpy(p->username,username,DB_STRING_LENGTH);
	    }
	}
	else
	{
	    strcpy(p->username,"");
	}

	db_set_client_state(p, DB_STATE_CLIENT_SENT_USERNAME);
	debug("[PARENT CLIENT PROCESS] Client sent username \"%s\"\n",tmp?username:"");
	log(LOG_NOTICE,"%s Authentication attempt for user \"%s\".\n",db_get_record_info(p),tmp?username:"");
	    
	/* send "Password" in base64 encoding */
	socket_printf(p->clientsocket,"+ UGFzc3dvcmQA\r\n");
	db_set_proxy_state(p,DB_STATE_PROXY_SENT_PASSWORD_REPLY_TO_CLIENT);
	
	goto c_real_end_process;
    }
    else if(db_get_client_state(p) == DB_STATE_CLIENT_SENT_USERNAME && db_get_proxy_state(p) == DB_STATE_PROXY_SENT_PASSWORD_REPLY_TO_CLIENT)
    {
	char password[DB_STRING_LENGTH+1];
	
	tmp = parsed_message_get_idtag(&pm);

	if(tmp)
	{
	    if(!strcmp(tmp,"*"))
	    {
		log(LOG_NOTICE,"%s User \"%s\" aborted login.\n",db_get_record_info(p),p->username);
		socket_printf(p->clientsocket,"%s BAD AUTHENTICATE LOGIN cancelled\r\n",p->clientauthtag);
		db_set_proxy_state(p,DB_STATE_PROXY_SENT_GREETING_TO_CLIENT);
		db_set_client_state(p,DB_STATE_CLIENT_NOT_AUTHENTICATED);
		free(p->clientauthtag);
		p->clientauthtag = NULL;
		strcpy(p->username,"");
		goto c_real_end_process;
	    }
	    else
	    {
		base64decode(password,tmp,strlen(tmp));
		strncpy(p->password,password,DB_STRING_LENGTH);
	    }
	}
	else
	{
	    strcpy(p->password,"");
	}
	
	
	db_set_client_state(p, DB_STATE_CLIENT_SENT_PASSWORD);
	debug("[PARENT CLIENT PROCESS] Client sent password\n");

	/* check if we have a free slot, otherwise connect to server */
	if(parent_locate_and_activate_record(p) == 0)
	    goto c_real_end_process;
	
	if(parent_init_server_connection(p))
	{
	    log(LOG_CRIT,"%s Could not initiate connection to server ?\n",db_get_record_info(p));
	    goto c_send_bye;
	}
	else
	    goto c_real_end_process;
    }

    /* we need at least 1 string to work with (ID tag) */
    if((argcount = parsed_message_count_args(&pm)) < 1)
    {
	socket_printf(p->clientsocket,"* BAD Null command\r\n");
	goto c_real_end_process;
    }


    if(argcount < 2)
    {
	socket_printf(p->clientsocket,"%s BAD Missing command\r\n",parsed_message_get_idtag(&pm));
	goto c_real_end_process;
    }
    
    tmp = parsed_message_get_command(&pm);
    
    if(db_get_proxy_state(p) == DB_STATE_PROXY_SENT_GREETING_TO_CLIENT && db_get_client_state(p) == DB_STATE_CLIENT_NOT_AUTHENTICATED && !strcasecmp(tmp,"AUTHENTICATE"))
    {
	if(argcount != 3)
	{
	    debug("parent_process_client_message(): AUTHENTICATE attempted with invalid amount of arguments (argcount = %d).\n",argcount);
	    socket_printf(p->clientsocket,"%s BAD Missing or invalid argument to AUTHENTICATE\r\n",parsed_message_get_idtag(&pm));
	    goto c_real_end_process;
	}
	
	tmp = parsed_message_get_arg(&pm,2);
	if(!strcasecmp(tmp,"LOGIN"))
	{
	    debug("[PARENT CLIENT PROCESS] Authenticated started (AUTHENTICATE LOGIN)\n");
	    db_set_client_state(p, DB_STATE_CLIENT_SENT_AUTHENTICATE_COMMAND);
	    
	    if(p->clientauthtag)
	    {
		debug("parent_process_client_message(): Previous tag message in place (%s)!\n",p->clientauthtag);
	    }
	    
	    if(!(p->clientauthtag = strdup(parsed_message_get_idtag(&pm))))
	    {
		debug("parent_process_client_message(): Out of memory\n");
		log(LOG_CRIT,"%s Out of memory\n",db_get_record_info(p));
		goto c_send_bye;
	    }
	    
	    /* send User Name in base64 */
	    socket_printf(p->clientsocket,"+ VXNlciBOYW1lAA==\r\n");
	    db_set_proxy_state(p,DB_STATE_PROXY_SENT_USERNAME_REPLY_TO_CLIENT);
	    
	    goto c_real_end_process;
	}
	else
	{
	    debug("parent_process_client_message(): Client sent unsupported authentication method (AUTHENTICATE %s)\n",tmp);
	    socket_printf(p->clientsocket,"%s NO AUTHENTICATE %s failed\r\n",parsed_message_get_idtag(&pm),parsed_message_get_arg(&pm,2));
	    
	    goto c_real_end_process;
	}
    }
    else if(db_get_proxy_state(p) == DB_STATE_PROXY_SENT_GREETING_TO_CLIENT && db_get_client_state(p) == DB_STATE_CLIENT_NOT_AUTHENTICATED && !strcasecmp(tmp,"LOGIN"))
    {	
	if(argcount < 4)
	{
	    debug("parent_process_client_message(): LOGIN attempted with invalid amount of arguments (argcount = %d).\n",argcount);
	    socket_printf(p->clientsocket,"%s BAD Missing or invalid argument to LOGIN\r\n",parsed_message_get_idtag(&pm));
	    goto c_real_end_process;
	}

	strncpy(p->username,parsed_message_get_arg(&pm,2),DB_STRING_LENGTH);
	strncpy(p->password,parsed_message_get_arg(&pm,3),DB_STRING_LENGTH);
	
	debug("[PARENT CLIENT PROCESS] LOGIN [%s] [*****]\n",p->username);
	log(LOG_NOTICE,"%s Authentication attempt for user \"%s\".\n",db_get_record_info(p),p->username);
	
	if(p->clientauthtag)
	{
	    debug("parent_process_client_message(): Previous tag message in place (%s)!\n",p->clientauthtag);
	    free(p->clientauthtag);
	    p->clientauthtag = NULL;
	}
	
	if(!(p->clientauthtag = strdup(parsed_message_get_idtag(&pm))))
	{
	    debug("parent_process_client_message(): Out of memory\n");
	    goto c_real_end_process;
	}

	db_set_client_state(p, DB_STATE_CLIENT_SENT_PASSWORD);

	/* if we find an inactive connection, activate it
	 * otherwise, open a new one.
	 */
	if(parent_locate_and_activate_record(p) == 0)
	    goto c_real_end_process;
	
	if(parent_init_server_connection(p))
	{
	    log(LOG_CRIT,"%s Could not initiate connection to server ?\n",db_get_record_info(p));
	    goto c_send_bye;
	}
	else
	    goto c_real_end_process;

    }
    else if(!strcasecmp(tmp,"LOGOUT"))
    {
	if(argcount != 2)
	{
	    debug("parent_process_client_message(): LOGOUT attempted with invalid amount of arguments (argcount = %d).\n",argcount);
	    socket_printf(p->clientsocket,"%s BAD\r\n",parsed_message_get_idtag(&pm));
	    goto c_real_end_process;
	}

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

	/* print a nice message for the client to stare at, and close the connection */
	socket_printf(p->clientsocket,"* BYE " PROGNAME " " VERSION " closing connection.\r\n");
	socket_printf(p->clientsocket,"%s OK LOGOUT completed\r\n",parsed_message_get_idtag(&pm));

	db_set_misc_state(p, DB_STATE_MISC_REMOVE_RECORD);
	
	goto c_real_end_process;
    }
    else if(!strcasecmp(tmp,"NOOP"))
    {
	socket_printf(p->clientsocket,"%s OK NOOP completed\r\n",parsed_message_get_idtag(&pm));
	goto c_real_end_process;
    }
    else if(!strcasecmp(tmp,"CAPABILITY"))
    {
	socket_printf(p->clientsocket,"* CAPABILITY IMAP4REV1 AUTH=LOGIN\r\n");
	socket_printf(p->clientsocket,"%s OK CAPABILITY completed\r\n",parsed_message_get_idtag(&pm));
	goto c_real_end_process;
    }
    else
    {
	socket_printf(p->clientsocket,"%s BAD Command unrecognized/login please: %s\r\n",parsed_message_get_idtag(&pm),parsed_message_get_command(&pm));
	goto c_real_end_process;
    }
	    
c_real_end_process: 
    delete_parsed_message(&pm);
    return 0;

c_send_bye:
    /*
     * something happened and we need to send a bye to the client.
     * this means :
     * 		send "BYE"
     * 		close the socket
     * 		set the correct states
     * 		clean the messages
     *
     * 	since we can only be here if the client didn't authenticate yet,
     * 	it means the state is DB_STATE_RECORD_TEMPORARY.
     * 	this also means that there is no lasting connection to the server yet
     * 	so, close the connection to the server as well, and remove the record all together.
     *
     */
    socket_printf(p->clientsocket,"* BYE " PROGNAME " " VERSION " closing connection.\r\n");
    delete_parsed_message(&pm);
    debug("parent error occured\n");

    db_set_misc_state(p,DB_STATE_MISC_REMOVE_RECORD);
    return -1;
}

/* process server message */
int parent_process_server_message(DB_record *p)
{
    struct message_t *currmessage = NULL;
    int i = 0;
    int argcount = 0;
    char *tmp = NULL;
    //Split_string ss;
    Parsed_message pm;
    
    currmessage = &(p->servermessage);
   
    if(config_enable_debug)
    {
	for(i = 0;i < currmessage->count; i++)
	{
	    if((tmp = db_get_server_message(p,i)))
	    {
		if(!i)
		    debug("[PS:] %.80s%s",tmp,strlen(tmp)>80?"...\n":"");
		else
		    debug("[PS:] ...\n");
	    }
	}
    }

    if(!(db_get_record_state(p) == DB_STATE_RECORD_ACTIVE || db_get_record_state(p) == DB_STATE_RECORD_INACTIVE || db_get_record_state(p) == DB_STATE_RECORD_TEMPORARY))
    {
	debug("parent_process_server_message(): We are in an impossible record state !!! %s\n",db_get_state_string(db_get_record_state(p)));
	return -1;
    }

    if(db_get_record_state(p) == DB_STATE_RECORD_INACTIVE)
    {
	/* if server sends a BYE, it will close the connection, and we'll clean it up in the next select() */
	return 0;
    }

    if(db_get_record_state(p) == DB_STATE_RECORD_ACTIVE)
    {
	debug("parent_process_server_message(): We should not be in DB_STATE_RECORD_ACTIVE state !! Did we forget to fork ?.\n");
	return -1;
    }

    /* we're now in TEMPORARY state */
   
    init_parsed_message(&pm);
    make_parsed_message(currmessage,&pm);
    print_parsed_message(&pm);
    
    if((argcount = parsed_message_count_args(&pm)) < 1)
    {
	debug("parent_process_server_message(): Server sent empty message\n");
	log(LOG_WARNING,"%s Server sent empty message\n",db_get_record_info(p));
	goto s_send_bye;
    }
   
    if(db_get_server_state(p) != DB_STATE_SERVER_NONE && db_get_proxy_state(p) != DB_STATE_PROXY_SENT_CAPABILITY_COMMAND_TO_SERVER && !strcmp(parsed_message_get_idtag(&pm),"*"))
    {
	/* if we are not expecting a greeting or the reply to the capability command, just treat this as a general notice */
	goto s_end_process;
    }
    
    if(db_get_server_state(p) == DB_STATE_SERVER_NONE)
    {
	char *respcode = NULL;
	if(argcount < 2)
	{
	    debug("parent_process_server_message(): Server greeting is not long enough.\n");
	    log(LOG_WARNING,"%s Server greeting is not long enough.\n",db_get_record_info(p));
	    goto s_send_bye;
	}

	/* condition is true if :
	 * the first argument is not "*"
	 * or
	 * the second argument is not one of "OK" "PREAUTH" "BYE"
	 */
	if(!(!strcmp(parsed_message_get_idtag(&pm),"*") &&	(!strcasecmp(parsed_message_get_command(&pm),"OK") || 
								!strcasecmp(parsed_message_get_command(&pm),"PREAUTH") ||
								!strcasecmp(parsed_message_get_command(&pm),"BYE"))))
	{
	    debug("parent_process_server_message(): Couldn't understand server (%s %s ...)\n",parsed_message_get_idtag(&pm),parsed_message_get_command(&pm));
	    log(LOG_WARNING,"%s Server sent weird command in DB_STATE_SERVER_NONE\n",db_get_record_info(p));
	    goto s_send_bye;
	}

	/* process the response code, because we care (really) */

	respcode = parsed_message_get_command(&pm);
	
	if(!strcasecmp(respcode,"BYE"))
	{
	    debug("parent_process_server_message(): Server sent a BYE ! should expect a panic close\n");
	    log(LOG_NOTICE,"%s Server sent BYE to proxy !\n",db_get_record_info(p));
	    goto s_send_bye;
	}

	/* otherwise, it's OK or PREAUTH */
	
	db_set_server_state(p,DB_STATE_SERVER_SENT_GREETING);

	debug("[PARENT SERVER PROCESS] Server sent Greeting\n");
	
	socket_printf(p->serversocket,"0 CAPABILITY\r\n");
	debug("parent_process_server_message(): We sent CAPABILITY command\n");
	if(!(p->proxyauthtag = strdup("0")))
	{
	    debug("parent_process_server_message(): Out of memory !\n");
	    log(LOG_CRIT,"%s Out of memory\n",db_get_record_info(p));
	    goto s_send_bye;
	}
	
	db_set_proxy_state(p,DB_STATE_PROXY_SENT_CAPABILITY_COMMAND_TO_SERVER);
	
	goto s_end_process;
    }
    else if(db_get_server_state(p) == DB_STATE_SERVER_SENT_GREETING && db_get_proxy_state(p) == DB_STATE_PROXY_SENT_CAPABILITY_COMMAND_TO_SERVER)
    {
	/* first check if its an untagged response ! if it's not, well, go on */

	if(!strcmp("*",parsed_message_get_idtag(&pm)))
	{
	    /* check if there is more than 1 word, and if so, if the second is "CAPABILITY" */
	    if(!((argcount > 1) && !strcasecmp("CAPABILITY",parsed_message_get_command(&pm))))
	    {
		/* no it isn't ... 
		 * silently leave without changing any states */
		goto s_end_process;
	    }
	    
	    /* check if we can use authenticate login */
	    if(parent_can_use_authenticate_login(&pm))
	    {
		db_set_capability_state(p,DB_STATE_CAPABILITY_AUTH_LOGIN);
	    }
	    else
	    {
		db_set_capability_state(p,DB_STATE_CAPABILITY_LOGIN);
	    }

	    goto s_end_process;
	}
	
	if(!p->proxyauthtag)
	{
	    debug("parent_process_server_message(): No recorded ID tag ?? This should not happen\n");
	    goto s_send_bye;
	}

	/* check if the given ID tag matches our request */
	if(strcmp(p->proxyauthtag,parsed_message_get_idtag(&pm)))
	{
	    debug("parent_process_server_message(): Server response does not match recorded client capability tag ! (\"%s\" <> \"%s\")\n",
		    parsed_message_get_idtag(&pm),p->clientauthtag);
	    log(LOG_WARNING,"%s Server returned an unknown ID tag\n",db_get_record_info(p));
	    goto s_send_bye;
	}

	/* free it */
	free(p->proxyauthtag);
	p->proxyauthtag = NULL;

	/* check for weird conditions (maybe the server sent NO ?) */

	if(strcasecmp(parsed_message_get_command(&pm),"OK"))
	{
	    debug("parent_process_server_message(): Server did not like CAPABILITY command ?!\n");
	    log(LOG_WARNING,"%s Server did not like CAPABILITY command.\n",db_get_record_info(p));
	    goto s_send_bye;
	}
	
	/* check whether we can use authenticate login */


	
	if(db_get_capability_state(p) == DB_STATE_CAPABILITY_AUTH_LOGIN)
	{
	    socket_printf(p->serversocket,"1 AUTHENTICATE LOGIN\r\n");
	}
	else
	{
	    /* we send the username and password as literals.
	     * this way, we avoid trouble with weird passwords that have \r\n in them and things
	     *
	     */
	    socket_printf(p->serversocket,"1 LOGIN {%d}\r\n",strlen(p->username));
	}
	
	/* we'll be using the same states for both authenticate login and the normal login
	 * to remember which method we used, later on
	 * we just have to look at the capability state.
	 */
	db_set_proxy_state(p,DB_STATE_PROXY_SENT_AUTHENTICATE_COMMAND_TO_SERVER);
	db_set_server_state(p,DB_STATE_SERVER_SENT_CAPABILITY_REPLY);
	
	/* remember the ID tag we used */
	if(!(p->proxyauthtag = strdup("1")))
	{
	    debug("parent_process_server_message(): Out of memory !\n");
	    log(LOG_CRIT,"%s Out of memory\n",db_get_record_info(p));
	    goto s_send_bye;
	}
	
	goto s_end_process;
    }
    else if(db_get_server_state(p) == DB_STATE_SERVER_SENT_CAPABILITY_REPLY && db_get_proxy_state(p) == DB_STATE_PROXY_SENT_AUTHENTICATE_COMMAND_TO_SERVER)
    {
	char buffer[DB_STRING_LENGTH+1];
	
	if(strcmp(parsed_message_get_idtag(&pm),"+"))
	{
	    debug("parent_process_server_message(): Couldn't understand server (%s ...)\n",parsed_message_get_idtag(&pm));
	    log(LOG_WARNING,"%s Server didn't continue with continuation of AUTHENTICATE command\n",db_get_record_info(p));
	    goto s_send_bye;
	}
#if 0
	base64decode(buffer,parsed_message_get_command(&pm),sizeof(buffer));

	/* Server should now send "User Name" or maybe "Username", as the rfc is not specific about this... */
	if(strcasecmp(buffer,"user name") && strcasecmp(buffer,"username"))
	{
	    debug("parent_process_server_message(): Couldn't understand server (+ %s ...) (Should send username)\n",buffer);
	    log(LOG_WARNING,"Server did not sent \"User Name\" or \"Username\" in base64 as a continuation command\n");
	    goto s_send_bye;
	}
#endif
	
	db_set_server_state(p,DB_STATE_SERVER_SENT_USERNAME_REPLY);
	debug("[PARENT SERVER PROCESS] Server sent continuation for username\n");

	if(db_get_capability_state(p) == DB_STATE_CAPABILITY_AUTH_LOGIN)
	{
	    memset(buffer,0,sizeof(buffer));
	    base64encode(buffer,p->username,strlen(p->username)+1);

	    socket_printf(p->serversocket,"%s\r\n",buffer);
	}
	else
	{
	    socket_printf(p->serversocket,"%s {%d}\r\n",p->username,strlen(p->password));
	}
	
	db_set_proxy_state(p,DB_STATE_PROXY_SENT_USERNAME_TO_SERVER);
	
	goto s_end_process;
    }
    else if(db_get_server_state(p) == DB_STATE_SERVER_SENT_USERNAME_REPLY && db_get_proxy_state(p) == DB_STATE_PROXY_SENT_USERNAME_TO_SERVER)
    {
	char buffer[DB_STRING_LENGTH+1];
	
	if(strcmp(parsed_message_get_idtag(&pm),"+"))
	{
	    debug("parent_process_server_message(): Couldn't understand server (%s ...)\n",parsed_message_get_idtag(&pm));
	    log(LOG_WARNING,"%s Server didn't continue with continuation of AUTHENTICATE command\n",db_get_record_info(p));
	    goto s_send_bye;
	}

#if 0
	base64decode(buffer,parsed_message_get_command(&pm),sizeof(buffer));

	if(strcasecmp(buffer,"password"))
	{
	    debug("parent_process_server_message(): Couldn't understand server (+ %s ...) (Should send username)\n",buffer);
	    log(LOG_WARNING,"Server did not sent \"Password\" in base64 as a continuation command\n");
	    goto s_send_bye;
	}

#endif
	
	db_set_server_state(p,DB_STATE_SERVER_SENT_PASSWORD_REPLY);
	
	debug("[PARENT SERVER PROCESS] Server sent continuation for password\n");

	if(db_get_capability_state(p) == DB_STATE_CAPABILITY_AUTH_LOGIN)
	{
	    memset(buffer,0,sizeof(buffer));
	    base64encode(buffer,p->password,strlen(p->password)+1);
	    socket_printf(p->serversocket,"%s\r\n",buffer);
	}
	else
	{
	    socket_printf(p->serversocket,"%s\r\n",p->password);
	}
	
	db_set_proxy_state(p,DB_STATE_PROXY_SENT_PASSWORD_TO_SERVER);
	
	goto s_end_process;
    }
    else if(db_get_server_state(p) == DB_STATE_SERVER_SENT_PASSWORD_REPLY && db_get_proxy_state(p) == DB_STATE_PROXY_SENT_PASSWORD_TO_SERVER)
    {
	char *tmp;
	
	if(!p->proxyauthtag)
	{
	    debug("parent_process_server_message(): No proxy auth tag recorded !\n");
	    log(LOG_WARNING,"%s Proxy didn't send ID tag ??\n",db_get_record_info(p));
	    goto s_send_bye;
	}
	
	if(strcmp(parsed_message_get_idtag(&pm),p->proxyauthtag))
	{
	    debug("parent_process_server_message(): Server response does not match recorded client auth tag ! (\"%s\" <> \"%s\")\n",parsed_message_get_idtag(&pm),
		    p->clientauthtag);
	    /* untagged response from server ?? */
	    log(LOG_WARNING,"%s Server returned an unknown ID tag\n",db_get_record_info(p));
	    goto s_send_bye;
	}
	
	free(p->proxyauthtag);
	p->proxyauthtag = NULL;
	
	/* this is a message with the response code of the authentication login */
	/* possible replies : OK, NO, BAD */

	tmp  = parsed_message_get_command(&pm);
	if(!strcasecmp(tmp,"OK"))
	{
	    debug("[PARENT SERVER PROCESS] Server says login ok\n");
	    socket_printf(p->clientsocket,"%s OK\r\n",p->clientauthtag);
	    db_set_client_state(p,DB_STATE_CLIENT_AUTHENTICATED);
	    db_set_proxy_state(p,DB_STATE_PROXY_SENT_LOGIN_OK_TO_CLIENT);
	    db_set_server_state(p,DB_STATE_SERVER_SENT_LOGIN_OK);
	    db_set_record_state(p,DB_STATE_RECORD_READY_TO_FORK);
	    log(LOG_INFO,"%s User \"%s\" logged in successfully.\n",db_get_record_info(p),p->username);
	    goto s_end_process;
	}
	else if(!strcasecmp(tmp,"NO"))
	{
	    debug("[PARENT SERVER PROCESS] Server says login failed\n");
	    socket_printf(p->clientsocket,"%s NO\r\n",p->clientauthtag);
	    db_set_proxy_state(p,DB_STATE_PROXY_SENT_LOGIN_FAILED_TO_CLIENT);
	    log(LOG_INFO,"%s User \"%s\" failed login.\n",db_get_record_info(p),p->username);
	    /* remove this record since it was temporary ! */
	    goto s_send_bye;
	}
	else if(!strcasecmp(tmp,"BAD"))
	{
	    debug("[PARENTSERVER PROCESS] Server claims bad command\n");
	    socket_printf(p->clientsocket,"%s BAD\r\n",p->clientauthtag);
	    db_set_proxy_state(p,DB_STATE_PROXY_SENT_LOGIN_FAILED_TO_CLIENT);
	    db_set_server_state(p,DB_STATE_SERVER_SENT_GREETING);
	    
	    log(LOG_CRIT,"%s Server send BAD reply to login attempt.\n",db_get_record_info(p));
	    /* remove this record since it was temporary ! */
	    goto s_send_bye;
	}

	debug("parent_process_server_message(): Server sent some weird reply (%s %s ...)\n",parsed_message_get_idtag(&pm),tmp);
	log(LOG_WARNING,"%s Server sent something weird\n",db_get_record_info(p));
	goto s_send_bye;
    }

s_end_process:
    delete_parsed_message(&pm);
    return 0;
    
s_send_bye:

    /*
     * if we get here, it means we can close the client and server socket, and free the record
     * no matter what the record state is
     */
   
    /* provide client and server with a clean exit */
    socket_printf(p->clientsocket,"* BYE " PROGNAME " " VERSION " closing connection.\r\n");
    socket_printf(p->serversocket,"0 LOGOUT\r\n");
    
    delete_parsed_message(&pm);
    debug("parent error occured\n");

    db_set_misc_state(p,DB_STATE_MISC_REMOVE_RECORD);
    return -1;

}

/* handle new data and parse it into a message */
int parent_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(what == INDEX_FOR_SERVER)
	currmessage = &(p->servermessage);
    else
	currmessage = &(p->clientmessage);
    
    buf = buffer;
    
    while(len > 0)
    {
	if(currmessage->literalcountdown > 0)
	{
	    /* we are in literal mode
	     * meaning : append everything to the message, untill we the literalcountdown is 0
	     */
	    if(currmessage->literalcountdown < len)
	    {
		n = currmessage->literalcountdown;
		if(!(tmpbuf = (char *)calloc(n+1,sizeof(char))))
		{
		    debug("parent_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("parent_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)
		    {
			parent_process_server_message(p);
			db_delete_server_message(p);
		    }
		    else
		    {
			parent_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
		    {
			/* server sends a continuation request for each literal */
			db_create_new_line_in_client_message(p);
			socket_printf(p->clientsocket,"+ Ready for argument\r\n");
		    }
		}

		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 parent_handle_client_connection_new_data(DB_record *p,char *buffer,int len)
{
    return parent_handle_connection_new_data(p,buffer,len,INDEX_FOR_CLIENT);
}

int parent_handle_server_connection_closed(DB_record *p)
{
    /* server connection closed, close client connection if any, and remove from database */

    debug("[PARENT SERVER CLOSED] <%d>\n",p->serversocket);

    /* inform only when a client is connected */
    if(p->clientsocket >= 0)
    {
	socket_printf(p->clientsocket,"* BYE\r\n");
    }
    
    db_set_misc_state(p,DB_STATE_MISC_REMOVE_RECORD);
    return 0;
}

int parent_handle_server_connection_error(DB_record *p)
{
    debug("parent_handle_server_connection_error(): read() error on socket %d (%m)\n",p->serversocket);
   
    /* inform only when a client is connected */
    if(p->clientsocket >= 0)
    {
	socket_printf(p->clientsocket,"* BYE\r\n");
    }
    
    db_set_misc_state(p,DB_STATE_MISC_REMOVE_RECORD);
    return 0;
}

int parent_handle_server_connection_new_data(DB_record *p,char *buffer,int len)
{
    return parent_handle_connection_new_data(p,buffer,len,INDEX_FOR_SERVER);
}

int parent_conversate(int fd)
{
    char buffer[1024];
    fd_set readset, writeset;
    struct timeval tv;

    int r = 0, rr = 0, maxfd = 0;
    DB_record *p,*q;
    
    bzero(buffer,sizeof(buffer));
    FD_ZERO(&readset);
    FD_ZERO(&writeset);
    FD_SET(fd,&readset);

#define SELECT_TIMEOUT_VALUE 60
    tv.tv_sec = SELECT_TIMEOUT_VALUE;
    tv.tv_usec = 0;

    maxfd = fd;
   
    
    /* check if we got a reload signal */
    if(rehash_flag)
    {
	rehash_flag = 0;

	debug("Reloading configfile...\n");
	
	if(config_file_read(option_config_file))
	{
	    debug("Error loading configfile !\n");
	    exit(0);
	}
    }

    
    /* add each socket from the records, to the readfdset */
    for(p = DB_start; p != NULL; p = p->next)
    {
	if(db_get_misc_state(p) == DB_STATE_MISC_REMOVE_RECORD)
	{
	    /* do nothing ! */
	}
	else
	if(db_get_record_state(p) == DB_STATE_RECORD_INACTIVE)
	{
	    /* if the server sends anything besides a close, throw it away
	     */
	    FD_SET(p->serversocket,&readset);
	    if(p->serversocket > maxfd)
		maxfd = p->serversocket;
	}
	else
	/* if the session is forked, only add the *pipe* to the readset
	 */
	if(db_get_record_state(p) == DB_STATE_RECORD_ACTIVE)
	{
	    /* record forked, so keep an eye on the ipc pipe */
	    FD_SET(p->ipcfd[0],&readset);
	    if(p->ipcfd[0] > maxfd)
		maxfd = p->ipcfd[0];
	}
	else
	{
	    /* DB_STATE_RECORD_TEMPORARY */

	    FD_SET(p->clientsocket,&readset);
	    if(p->clientsocket > maxfd)
		maxfd = p->clientsocket;
	    
	    /* we need to know if the nonblocking server-socket has connected successfully
	     * only add to writeset if serversocket is in non-connected mode ! */
	    if(db_get_server_connection_state(p) == DB_STATE_SERVER_CONNECTION_STARTED)
		FD_SET(p->serversocket,&writeset);
	   
	    if(db_get_server_connection_state(p) != DB_STATE_SERVER_NOT_CONNECTED)
	    {
		FD_SET(p->serversocket,&readset);
		if(p->serversocket > maxfd)
		    maxfd = p->serversocket;
	    }
	}
    }
    
    maxfd++;
    
    if((r = select(maxfd,&readset,&writeset,NULL,&tv)) > 0)
    {
	/* reset Globaltime, because select() might have been blocking for a while */
	Globaltime = time(NULL);

	/* new connection ? */
	if(FD_ISSET(fd,&readset))
	{
	    parent_handle_new_connection(fd);
	}
	
	p = DB_start;

	/* check the status of the socket for every record */
	while(p)
	{
	    q = p->next;

	    /* first of all
	     * if we marked another record than the current, for removal
	     * (as is the case with a reactivation of an inactive record)
	     * we need to check if the old record has the remove flag set.
	     * if it has, then skip ahead to the remove part, and don't check it's sockets.
	     */

	    if(db_get_misc_state(p) == DB_STATE_MISC_REMOVE_RECORD)
	    {
		goto all_your_record_are_belong_to_us;
	    }
	    
	    /* IMPORTANT NOTE:
	     * 	while checking the sockets, it is possible that a DB_record was removed.
	     * 	so it is important not to do anything else with the DB_record, once its connections have been checked.
	     */

	    /* if this is a forked record's parent, only check the ipcfd */
	    if((db_get_record_state(p) == DB_STATE_RECORD_ACTIVE))
	    {
		if(FD_ISSET(p->ipcfd[0],&readset))
		{
		    int retcode = 0;
		    int temppid = 0;
		    
		    rr = read(p->ipcfd[0],&retcode,sizeof(int));
                    
		    if(rr < 0)
                    {
                        debug("parent_conversate(): Error reading from ipc socket.\n");
			return -1;
                    }
                    else if(rr == 0)
                    {
                        debug("parent_conversate(): Child closed ipc socket.\n");
			/* kill child ! and remove structure. */
			debug("killing process ID %d because of closed connection\n",p->pid);
			kill(p->pid,9);
			p->pid = -1;

			db_set_misc_state(p,DB_STATE_MISC_REMOVE_RECORD);
                    }
                    else
                    {
			/* if we get here, it means that the forked process has finished.
			 * this means the session is done.
			 * so we can clean up the mess, and make the record available again
			 * (if the server didn't close the connection)
			 */
			
			log(LOG_INFO,"%s Session for user \"%s\" terminated.\n",db_get_record_info(p),p->username);
			temppid = p->pid;
			debug("cleaning up record with pid %d\n",temppid);
			db_cleanup(p);
			debug("killing process ID %d\n",temppid);
			kill(temppid,9);
			db_set_record_state(p,DB_STATE_RECORD_INACTIVE);

			switch(retcode)
			{
			    case DB_IPC_RETURNCODE_NORMAL:
				debug("parent_conversate(): Child reports normal exit.\n");
				break;
			    case DB_IPC_RETURNCODE_SERVER_CLOSED:
				debug("parent_conversate(): Child reports server closed connection.\n");
				close(p->serversocket);
				p->serversocket = -1;
				db_del(p);
				break;
			    case DB_IPC_RETURNCODE_SERVER_ERROR:
				debug("parent_conversate(): Child reports error on server connection.\n");
				close(p->serversocket);
				p->serversocket = -1;
				db_del(p);
				break;
			    case DB_IPC_RETURNCODE_CLIENT_CLOSED:
				debug("parent_conversate(): Child reports client closed connection.\n");
				break;
			    case DB_IPC_RETURNCODE_CLIENT_ERROR:
				debug("parent_conversate(): Child reports error on client connection.\n");
				break;
			}

			/* if we have used this record too often, remove it. */
			if(p->reuse >= config_max_reuse && p->serversocket != -1)
			{
			    log(LOG_INFO,"%s Record has been reused %d times. Removing it...\n",db_get_record_info(p),p->reuse);
			    socket_printf(p->serversocket,"0 LOGOUT\r\n");
			    close(p->serversocket);
			    db_del(p);
			}
                    }
		}
	    }
	    else /* don't read from client socket unless we are a temporary record */
		if((db_get_record_state(p) == DB_STATE_RECORD_TEMPORARY) && FD_ISSET(p->clientsocket,&readset))
	    {
		rr = read(p->clientsocket,buffer,sizeof(buffer)-1);

		p->clientidlesince = Globaltime;
		
		if(rr < 0)
		{
		    parent_handle_client_connection_error(p);
		}
		else if(rr == 0)
		{
		    parent_handle_client_connection_closed(p);
		}
		else
		{
		    buffer[rr] = '\0';
		    parent_handle_client_connection_new_data(p,buffer,rr);
		}
	    }
	    else /* only check if server is connected, if we started a connection, and if we are in temporary state */
		if((db_get_server_connection_state(p) == DB_STATE_SERVER_CONNECTION_STARTED) &&
			(db_get_record_state(p) == DB_STATE_RECORD_TEMPORARY) && FD_ISSET(p->serversocket,&writeset))
	    {
		int sockval = 0;
		int ret;
		int len = sizeof(int);
		
		ret = getsockopt(p->serversocket, SOL_SOCKET, SO_ERROR, &sockval, &len);

		if(ret < 0)
		{
		    /* panic and jump out of the window */
		    debug("parent_conversate(): getsockopt error (%m) CLOSE\n");
		    
		    /* tell client to leave */
		    parent_handle_server_connection_closed(p);
		    
		    db_set_misc_state(p,DB_STATE_MISC_REMOVE_RECORD);
		}
		else switch(sockval)
		{
		    case 0:
			/* connected */
			debug("parent_conversate(): Server connected ! (client %d - server %d)\n",p->clientsocket,p->serversocket);
			db_set_server_connection_state(p, DB_STATE_SERVER_CONNECTED);
			break;
		    default:
			/* not connected */
			debug("parent_conversate(): Connection to server failed. CLOSE\n");
			parent_handle_server_connection_closed(p);
			break;
			
		}
	    }
	    else /* only read from the server socket if it is connected, and if the record is not active */
	    if((db_get_server_connection_state(p) != DB_STATE_SERVER_NOT_CONNECTED) &&
		    (db_get_record_state(p) != DB_STATE_RECORD_ACTIVE) && FD_ISSET(p->serversocket,&readset))
	    {
		rr = read(p->serversocket,buffer,sizeof(buffer)-1);
		
		if(rr < 0)
		{
		    parent_handle_server_connection_error(p);
		}
		else if(rr == 0)
		{
		    parent_handle_server_connection_closed(p);
		}
		else
		{
		    buffer[rr] = '\0';
		    parent_handle_server_connection_new_data(p,buffer,rr);
		}
	    }

	    /* if client is authenticated, fork here */
	    if(     db_get_record_state(p) == DB_STATE_RECORD_READY_TO_FORK && 
		    db_get_misc_state(p) != DB_STATE_MISC_REMOVE_RECORD && 
		    db_get_client_state(p) == DB_STATE_CLIENT_AUTHENTICATED)
	    {
		rr = parent_fork_record(p,fd);
		if(rr <= 0)
		    return rr;
	    }
	    
all_your_record_are_belong_to_us:
	    
	    /* if record was flagged for removal, then remove it ! */
	    if(db_get_misc_state(p) == DB_STATE_MISC_REMOVE_RECORD)
	    {
		if(p->pid > 0)
		{
		    debug("parent_conversate(): while removing record, child still exists ? pid = %d\n",p->pid);
		}
		
		db_del(p);
	    }

	    p = q;
	}
    }
    else if(!r)
    {
	debug("parent_conversate(): select() timed out. (%d seconds without activity)\n",SELECT_TIMEOUT_VALUE);
	return 1;
    }
    else
    {
	if(errno != EINTR)
	{
	    debug("parent_conversate(): select() error (%m)\n");
	    return -1;
	}
    }

    return -1;
}


/* this function checks for idle connections
 * and acts upon them
 *
 * we close idle client connections
 *
 * we send NOOP's over inactive serverconnections for some time
 * we close idle server connections after some other time
 */
int parent_handle_idlers()
{
    DB_record *p,*q;

    p = DB_start;

    while(p)
    {
	q = p->next;

	if(db_get_record_state(p) == DB_STATE_RECORD_INACTIVE)
	{
	    if(p->serveridlesince > 0 && (Globaltime - p->serveridlesince > config_server_timeout))
	    {
		debug("parent_handle_idlers(): Server %d has been inactive for too long (%d s)... closing.\n",p->serversocket,Globaltime - p->serveridlesince);
		/* connection was open too long, gracefully close it */
		if(p->serversocket >= 0)
		{
		    socket_printf(p->serversocket,"0 LOGOUT\r\n");
		}

		db_del(p);
	    }
	    else
	    if(p->serverlastnoop > 0 && (Globaltime - p->serverlastnoop > config_keepalive))
	    {
		/* it's been too long since we last sent a NOOP
		 */
		
		debug("parent_handle_idlers(): Sending NOOP to server %d.\n",p->serversocket);
		socket_printf(p->serversocket,"0 NOOP\r\n");
		p->serverlastnoop = Globaltime;
	    }
	    
	}
	else if(db_get_record_state(p) == DB_STATE_RECORD_TEMPORARY)
	{
	    if(p->clientidlesince > 0 && (Globaltime - p->clientidlesince > config_client_timeout))
	    {
		/* client is just sitting there, remove it */
		debug("parent_handle_idlers(): Client %d idle for too long: closing.\n",p->clientsocket);

		if(p->clientsocket >= 0)
		{
		    socket_printf(p->clientsocket,"* BYE Autologout after %d seconds.\r\n",config_client_timeout);
		}

		db_del(p);
	    }
	}
	
	p = q;
    }

    return 0;
}














syntax highlighted by Code2HTML, v. 0.9.1