/*
** 
**               Copyright (c) 2002,2003 Dave McMurtrie
**		 Copyright (c) 2004 Martin Blapp
**
** This file is part of pop3proxy, a descendant of Dave McMurtrie's pop3proxy.
**
** pop3proxy 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.
**
** pop3proxy 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 pop3proxy; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
**
**
**  Facility:
**
**	request.c
**
**  Abstract:
**
**	Routines to handle IMAP client requests...  Specifically, this module
**	contains the handler thread (Handle_Request) that takes care of
**	incoming client connections.  It also contains one function for each
**	of the 5 unauthenticated commands that the proxy server can handle
**	without having to make a connection to the real IMAP server.  Finally,
**	the raw proxy function resides in this module, as well.
**
**  Authors:
**
**	Dave McMurtrie <davemcmurtrie@hotmail.com>
**
**  RCS:
**
**	$Source: /afs/pitt.edu/usr12/dgm/work/IMAP_Proxy/src/RCS/request.c,v $
**	$Id: request.c,v 1.17 2003/10/09 14:11:13 dgm Exp $
**      
**  Modification History:
**
**	$Log: request.c,v $
**	Revision 1.17  2003/10/09 14:11:13  dgm
**	bugfix: set TotalClientLogins to zero in cmd_resetcounters, submitted
**	by Geoffrey Hort <g.hort@unsw.edu.au>.  Changes to allow syslogging of the
**	client source port.  Added timestamps to protocol log entries.
**
**	Revision 1.16  2003/07/14 16:26:02  dgm
**	Removed erroneous newline from syslog() call to prevent compiler
**	warning.
**
**	Revision 1.15  2003/07/07 13:31:09  dgm
**	Bugfix by Gary Windham <windhamg@email.arizona.edu>.  Raw_Proxy() loop
**	was not dealing with string literals correctly when the server
**	responded with something other than a '+'.
**
**	Revision 1.14  2003/05/20 19:11:25  dgm
**	Comment changes only.
**
**	Revision 1.13  2003/05/15 11:35:39  dgm
**	Patch by Ken Murchison <ken@oceana.com> to clean up build process:
**	conditionally include sys/param.h instead of defining MAXPATHLEN.
**
**	Revision 1.12  2003/05/13 11:41:26  dgm
**	Patches by Ken Murchison <ken@oceana.com> to clean up build process.
**
**	Revision 1.11  2003/05/08 17:20:43  dgm
**	Added code to send untagged server responses back to clients
**	on LOGOUT.
**
**	Revision 1.10  2003/05/06 12:11:47  dgm
**	Applied patches by Ken Murchison <ken@oceana.com> to include SSL
**	support.
**
**	Revision 1.9  2003/02/20 12:57:26  dgm
**	Raw_Proxy() sends UNSELECT instead of CLOSE if the server supports it.
**
**	Revision 1.8  2003/02/19 12:57:15  dgm
**	Added support for "AUTHENTICATE LOGIN".
**
**	Revision 1.7  2003/02/14 18:25:40  dgm
**	Fixed bug in cmd_newlog.  ftruncate doesn't reset file pointer so
**	I added an lseek.
**
**	Revision 1.6  2003/01/22 13:03:25  dgm
**	Changes to HandleRequest() and cmd_login() to support clients that
**	send the password as a literal string on login.
**
**	Revision 1.5  2002/08/30 13:24:43  dgm
**	Added support for total client logins counter
**
**	Revision 1.4  2002/08/28 15:59:25  dgm
**	replaced all internal log function calls with standard syslog calls.
**	Added P_RESETCOUNTERS command.
**	Added logic to time out connections in the raw proxy loop.
**
**	Revision 1.3  2002/08/27 20:15:16  dgm
**	No longer bother doing a dns lookup before calling Get_Server_sd().
**	We'll only pass the ip address string instead of possibly hostname.
**
**	Revision 1.2  2002/07/18 20:43:17  dgm
**	added p_dumpicc and p_newlog commands.  Changed trace
**	command to p_trace.
**
**	Revision 1.1  2002/07/03 12:08:34  dgm
**	Initial revision
**
**
*/


#define _REENTRANT

#include <errno.h>
#include <string.h>
#include "common.h"
#include "pop3proxy.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <poll.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <syslog.h>
#if HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif

#include <openssl/evp.h>

/*
 * There are a few global variables that we care about.  Make sure we know
 * about them here.
 */
extern char Banner[BUFSIZE];
extern int BannerLen;
extern char Capability[BUFSIZE];
extern int CapabilityLen;
extern char Epoplist[BUFSIZE];
extern int EpoplistLen;
extern IMAPCounter_Struct *IMAPCount;
extern ISD_Struct ISD;
extern pthread_mutex_t mp;
extern pthread_mutex_t trace;
extern char TraceUser[MAXUSERNAMELEN];
extern int Tracefd;
extern ICC_Struct *ICC_HashTable[ HASH_TABLE_SIZE ];
extern ProxyConfig_Struct PC_Struct;

/*
 * Function prototypes for internal entry points.
 */
static int cmd_noop( ITD_Struct *, char * );
static int cmd_logout( ITD_Struct *, char * );
static int cmd_capability( ITD_Struct *, char * );
static int cmd_authenticate_login( ITD_Struct *, char * );
static int cmd_login( ITD_Struct *, char *, char *, int, char *, unsigned char );
static int cmd_trace( ITD_Struct *, char *, char * );
static int cmd_dumpicc( ITD_Struct *, char * );
static int cmd_newlog( ITD_Struct *, char * );
static int cmd_resetcounters( ITD_Struct *, char * );
static int Raw_Proxy( ITD_Struct *, ITD_Struct * );



/*
 * Function definitions.
 */

/*++
 * Function:	cmd_newlog
 *
 * Purpose:	Clear the proxy trace log file.
 *
 * Parameters:	ptr to ITD_Struct for client connection.
 *              char ptr to Tag sent with this command.
 *
 * Returns:	0 on success
 *		-1 on failure
 *
 * Authors:     Dave McMurtrie <davemcmurtrie@hotmail.com>
 *--
 */
static int cmd_newlog( ITD_Struct *itd, char *Tag )
{
    char *fn = "cmd_newlog";
    char SendBuf[BUFSIZE];
    unsigned int BufLen = BUFSIZE - 1;
    int rc;
    
    SendBuf[BUFSIZE - 1] = '\0';

    rc = ftruncate( Tracefd, 0 );
    
    if ( rc != 0 )
    {
	syslog(LOG_ERR, "%s: ftruncate() failed: %s", fn, strerror( errno ) );
	snprintf( SendBuf, BufLen, "%s NO internal server error\r\n", Tag );

	if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
	{
	    syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
	    return( -1 );
	}
	
	return( -1 );
    }
    
    /*
     * bugfix.  ftruncate doesn't reset the file pointer...
     */
    rc = lseek( Tracefd, 0, SEEK_SET );
    
    if ( rc < 0 )
    {
	syslog(LOG_ERR, "%s: lseek() failed: %s", fn, strerror( errno ) );
	snprintf( SendBuf, BufLen, "%s NO internal server error\r\n", Tag );
	
	if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
	{
	    syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
	    return( -1 );
	}
	
	return( -1 );
    }

    snprintf( SendBuf, BufLen, "%s OK Completed\r\n", Tag );
    
    if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
    {
	syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
	return( -1 );
    }
    
    return( 0 );
}


/*++
 * Function:	cmd_resetcounters
 *
 * Purpose:	Reset the global high-water marks and total counters.
 *
 * Parameters:	ptr to ITD_Struct for client connection.
 *		char ptr to Tag sent with this command.
 *
 * Returns:	0 on success.
 *		-1 on failure.
 *
 * Authors:	Dave McMurtrie <davemcmurtrie@hotmail.com>
 *
 * Notes:	Always key to remember that we don't take out a mutex
 *		anywhere that we update these global counters.  There's
 *		never a guarantee that they'll be exactly correct but
 *		for the performance penalty we'd pay to make them correct
 *		we just don't care.
 *--
 */
static int cmd_resetcounters( ITD_Struct *itd, char *Tag )
{
    char *fn = "cmd_resetcounters";
    char SendBuf[BUFSIZE];
    unsigned int BufLen = BUFSIZE -1;
    
    SendBuf[BufLen] = '\0';

    /*
     * Bugfix by Geoffrey Hort <g.hort@unsw.edu.au> -- I forgot to zero
     * out TotalClientLogins...
     */
    IMAPCount->CountTime = time( 0 );
    IMAPCount->PeakClientConnections = 0;
    IMAPCount->PeakInUseServerConnections = 0;
    IMAPCount->PeakRetainedServerConnections = 0;
    IMAPCount->TotalClientConnectionsAccepted = 0;
    IMAPCount->TotalServerConnectionsCreated = 0;
    IMAPCount->TotalServerConnectionsReused = 0;
    IMAPCount->TotalClientLogins = 0;
    
    snprintf( SendBuf, BufLen, "%s OK Completed\r\n", Tag );
    
    if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
    {
	syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
	return( -1 );
    }
    
    return( 0 );
}



/*++
 * Function:	cmd_dumpicc
 *
 * Purpose:	Dump the contents of all imap connection context structs.
 *
 * Parameters:	ptr to ITD_Struct for client connection.
 *              char ptr to Tag sent with this command.
 *
 * Returns:	0 on success
 *		-1 on failure
 *
 * Authors:     Dave McMurtrie <davemcmurtrie@hotmail.com>
 *--
 */
static int cmd_dumpicc( ITD_Struct *itd, char *Tag )
{
    char *fn = "cmd_dumpicc";
    char SendBuf[BUFSIZE];
    unsigned int HashIndex;
    ICC_Struct *HashEntry;
    unsigned int BufLen = BUFSIZE - 1;
    
    SendBuf[BUFSIZE - 1] = '\0';
    
    LockMutex( &mp );
    
    for ( HashIndex = 0; HashIndex < HASH_TABLE_SIZE; HashIndex++ )
    {
	HashEntry = ICC_HashTable[ HashIndex ];
	
	while ( HashEntry )
	{
	    snprintf( SendBuf, BufLen, "* %d %s %s\r\n", HashEntry->server_conn->sd,
		      HashEntry->username,
		      ( ( HashEntry->logouttime ) ? "Cached" : "Active" ) );
	    if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
	    {
		UnLockMutex( &mp );
		syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
		return( -1 );
	    }
	    HashEntry = HashEntry->next;
	}
    }
    
    UnLockMutex( &mp );
    
    snprintf( SendBuf, BufLen, "%s OK Completed\r\n", Tag );
    if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
    {
	syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
	return( -1 );
    }
    
    return( 0 );
}



/*++
 * Function:	cmd_trace
 *
 * Purpose:	turn on per-user tracing in the proxy server.
 *
 * Parameters:	ptr to ITD_Struct for client connection.
 *              char ptr to Tag sent with this command.
 *              char ptr to the username we want to trace (NULL to turn
 *              off tracing)
 *
 * Returns:	0 on success
 *		-1 on failure
 *
 * Authors:     Dave McMurtrie <davemcmurtrie@hotmail.com>
 *--
 */
static int cmd_trace( ITD_Struct *itd, char *Tag, char *Username )
{
    char *fn = "cmd_trace";
    char SendBuf[BUFSIZE];
    unsigned int BufLen = BUFSIZE - 1;
    
    SendBuf[BUFSIZE - 1] = '\0';
    
    /*
     * Here are the tracing semantics:
     *
     * Tracing is to be limited to only one user at a time.  This decision was
     * made for a few different reasons.  First, to conserve system resources
     * such as disk space.  Second, to improve overall server performance --
     * tracing will slow a thread down.  Third, so a sysadmin doesn't forget
     * that tracing is turned on for a user (like I commonly do when I enable
     * tracing in cyrus imapd).  Fourth, it's just easier this way.
     */    
    
    LockMutex( &trace );
    
    if ( !Username )
    {
	snprintf( SendBuf, BufLen, "\n\n-----> C= %d %s PROXY: user tracing disabled. Expect further output until client logout.\n", time( 0 ), TraceUser );
	write( Tracefd, SendBuf, strlen( SendBuf ) );
	
	memset( TraceUser, 0, sizeof TraceUser );
	snprintf( SendBuf, BufLen, "%s OK Tracing disabled\r\n", Tag );
	if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
	{
	    syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
	    UnLockMutex( &trace );
	    return( -1 );
	}

	UnLockMutex( &trace );
	return( 0 );
    }

    if ( TraceUser[0] )
    {
	/* guarantee no runaway strings */
	TraceUser[sizeof TraceUser - 1] = '\0';
	snprintf( SendBuf, BufLen, "%s BAD Tracing already enabled for user %s\r\n", Tag, TraceUser );
	if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
	{
	    syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
	    UnLockMutex( &trace );
	    return( -1 );
	}
	
	UnLockMutex( &trace );
	return( 0 );
	
    }
    
    strncpy( TraceUser, Username, sizeof TraceUser - 1 );
    
    snprintf( SendBuf, BufLen, "%s OK Tracing enabled\r\n", Tag );
    if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
    {
	syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
	UnLockMutex( &trace );
	return( -1 );
    }

    snprintf( SendBuf, BufLen, "\n\n-----> C= %d %s PROXY: user tracing enabled.\n", time( 0 ), TraceUser );
    write( Tracefd, SendBuf, strlen( SendBuf ) );
    
    UnLockMutex( &trace );
    return( 0 );
}



/*++
 * Function:	cmd_noop
 *
 * Purpose:	implement the NOOP IMAP command.
 *
 * Parameters:	ptr to ITD_Struct for client connection.
 *
 * Returns:	0 on success
 *		-1 on failure
 *
 * Authors:     Dave McMurtrie <davemcmurtrie@hotmail.com>
 *--
 */
static int cmd_noop( ITD_Struct *itd, char *Tag )
{
    char *fn = "cmd_noop";
    char SendBuf[BUFSIZE];
    unsigned int BufLen = BUFSIZE - 1;
    
    SendBuf[BUFSIZE - 1] = '\0';
    
    snprintf( SendBuf, BufLen, "%s OK Completed\r\n", Tag );
    if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
    {
	syslog(LOG_WARNING, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );
	return( -1 );
    }
    
    return( 0 );
}



/*++
 * Function:	cmd_logout
 *
 * Purpose:	implement the LOGOUT IMAP command.
 *
 * Parameters:	ptr to ITD_Struct for client connection.
 *
 * Returns:	0 on success
 *		-1 on failure
 *
 * Authors:     Dave McMurtrie <davemcmurtrie@hotmail.com>
 *--
 */
static int cmd_logout( ITD_Struct *itd, char *Tag )
{
    char *fn = "cmd_logout";
    char SendBuf[BUFSIZE];
    unsigned int BufLen = BUFSIZE - 1;
    
    SendBuf[BUFSIZE - 1] = '\0';
    
    snprintf( SendBuf, BufLen, "+OK Pop server at virtualdomain signing off.\r\n",
	      Tag );
    if ( IMAP_Write( itd->conn, SendBuf, strlen(SendBuf) ) == -1 )
    {
	syslog(LOG_WARNING, "%s: IMAP_Write() to client failed on sd [%d]: %s", fn, itd->conn->sd, strerror(errno) );
	return( -1 );
    }
    
    return( 0 );
}

/*++
 * Function:	cmd_login
 *
 * Purpose:	implement the LOGIN IMAP command.
 *
 * Parameters:	ptr to ITD_Struct for client connection.
 *              ptr to username
 *              ptr to password
 *              int length of password buffer
 *              ptr to client tag
 *              unsigned char - flag to indicate literal password in login
 *                              command.
 *
 * Returns:	0 on success prior to authentication
 *              1 on success after authentication (we caught a logout)
 *		-1 on failure
 *
 * Authors:     Dave McMurtrie <davemcmurtrie@hotmail.com>
 *
 * Note:        Not too many things are really considered "failure" in the
 *              context of this routine, because returning failure would
 *              result in the client connection being closed.  When most
 *              things fail, we'll send a failure response to the client,
 *              but return success to the caller.  That will allow the
 *              client to know that something broke and try again if it
 *              wants to.
 *
 *--
 */
static int cmd_login( ITD_Struct *Client, 
		      char *Username, 
		      char *Password,
		      int passlen,
		      char *Tag,
		      unsigned char LiteralLogin )
{
    char *fn = "cmd_login()";
    char SendBuf[BUFSIZE];
    unsigned int BufLen = BUFSIZE - 1;
    ITD_Struct Server;
    int rc;
    ICD_Struct *conn;
    char TraceFileName[ MAXPATHLEN ];
    struct sockaddr_in cli_addr;
    int addrlen;
    char *hostaddr;
    in_port_t sin_port;

    memset( &Server, 0, sizeof Server );

    addrlen = sizeof( struct sockaddr_in );

    if ( getpeername( Client->conn->sd, (struct sockaddr *)&cli_addr, &addrlen ) < 0 )
    {
	syslog(LOG_INFO, "LOGIN: '%s' failed: getpeername() failed for client sd: %s", Username, strerror( errno ) );
	return( -1 );
    }
    
    hostaddr = inet_ntoa( ( ( struct sockaddr_in *)&cli_addr )->sin_addr );
    sin_port = ntohs( cli_addr.sin_port );

    if ( !hostaddr )
    {
	syslog(LOG_INFO, "LOGIN: '%s' failed: inet_ntoa() failed for client sd: %s", Username, strerror( errno ) );
	return( -1 );
    }
    
    conn = Get_Server_conn( Username, Password, hostaddr, sin_port, LiteralLogin, Client );

    /*
     * wipe out the passwd so we don't have it sitting in memory somewhere.
     */
    memset( Password, 0, passlen );
        
    if ( conn == NULL )
    {
	/*
	 * All logging is done in Get_Server_conn, so don't bother to
	 * log anything here.
	 */
	cmd_logout( Client, Tag );
	/*
	* close the client side socket.
	*/
	close( Client->conn->sd );
	return( -1 );
    }
    
    Server.conn = conn;

    /*
     * Send a success message back to the client
     * and go into raw proxy mode.
     */
    snprintf( SendBuf, BufLen, "+OK %s logged in\r\n", Username );
    if ( IMAP_Write( Client->conn, SendBuf, strlen(SendBuf) ) == -1 )
    {
	/*
	 * This really sux.  We successfully logged the user in, but now
	 * we can't communicate with the client...
	 */
	IMAPCount->InUseServerConnections--;
	close( Server.conn->sd );
	syslog(LOG_ERR, "%s: Unable to send successful login message back to client: %s -- closing connection.", fn, strerror(errno) );
	return( -1 );
    }

    IMAPCount->TotalClientLogins++;
    
    /* turn on tracing for this session if necessary */
    LockMutex( &trace );
    if ( ! strcmp( Username, TraceUser ) )
    {
	Client->TraceOn = 1;
	Server.TraceOn = 1;
    }
    else
    {
	Client->TraceOn = 0;
	Server.TraceOn = 0;
    }
    UnLockMutex( &trace );

    rc = Raw_Proxy( Client, &Server );

    /*
     * It's not necessary to take out the trace mutex here.  The reason
     * we take it out when we check above is because the trace username
     * could change at any time.  When we disable tracing here, we're
     * doing it regardless of what the trace username is, so we don't 
     * take out the mutex.
     */
    Client->TraceOn = 0;
    Server.TraceOn = 0;
    
    /* update the logout time for this cached connection */
    ICC_Logout( Username, Server.conn );
    
    return( rc );
}




/*++
 * Function:	Raw_Proxy
 *
 * Purpose:	Start a raw proxy session to the IMAP server, catching only
 *		LOGOUT commands from the client.
 *
 * Parameters:	ptr to client ITD_Struct
 *		ptr to server ITD_Struct
 *
 * Returns:	1 if we caught a logout
 *		-1 on failure
 *
 * Authors:	Dave McMurtrie <davemcmurtrie@hotmail.com>
 *--
 */
static int Raw_Proxy( ITD_Struct *Client, ITD_Struct *Server )
{
    char *fn = "Raw_Proxy()";
    struct pollfd fds[2];
    nfds_t nfds;
    int status, pending;
    unsigned int FailCount;
    int BytesSent;
    char *CP;
    char TraceBuf[ BUFSIZE ];
    char SendBuf[ BUFSIZE ];
    
#define SERVER 0
#define CLIENT 1
    
    FailCount = 0;
    nfds = 2;
    
    /*
     * Set up our fds structs.
     */
    fds[ SERVER ].fd = Server->conn->sd;
    fds[ CLIENT ].fd = Client->conn->sd;
    
    fds[ SERVER ].events = POLLIN;
    fds[ CLIENT ].events = POLLIN;

    /*
     * POLL loop
     */
    for ( ; ; )
    {
	pending = 0;
	fds[ SERVER ].revents = 0;
	fds[ CLIENT ].revents = 0;
	
#if HAVE_LIBSSL
#if USE_POP3
	if ( Server->conn->tls )
	{
	    /* See is we have any buffered input */
	    pending = SSL_pending( Server->conn->tls );
	}
#endif
#endif

	status = ( pending ? 1 : poll( fds, nfds, POLL_TIMEOUT ) );
	
	/*
	 * poll returns a non-negative value on success.
	 * it returns 0 if it times out before any revents are modified.
	 * -1 is returned on failure.
	 */
	if ( !status )
	{
	    /*
	     * We timed out.  End result is that we want the server and
	     * client sides of this connection to close.  The nicest way
	     * to have this happen will be to simply return failure to
	     * cmd_login().
	     * We were called by HandleRequest() & then cmd_login().  When
	     * we return -1 to cmd_login(), he'll call ICC_Logout() to
	     * update the logout time for this user on this server sd.
	     * Eventually, the ICC_Recycle thread will wake up and reap the
	     * server-side of the connection.
	     * cmd_login() will return our -1 to Handle_Request
	     * and HandleRequest will close the client-side socket.
	     */
	    syslog( LOG_WARNING, "%s: poll() timed out. server sd [%d]. client sd [%d].", fn, Server->conn->sd, Client->conn->sd );
	    return( -1 );
	}
	
	if ( status < 0 )
	{
	    /* If we were interrupted by a signal, just continue the loop. */
	    if ( errno == EINTR )
	    {
		syslog(LOG_INFO, "%s: poll() was interrupted by a signal -- continuing.", fn);
		continue;
	    }
	    
	    
	    /* resource issue -- try again. */
	    if ( errno == EAGAIN )
	    {
		FailCount++;
		if ( FailCount == 5 )
		{
		    syslog(LOG_ERR, "%s: poll() returned EAGAIN.  Exceeded retry limit.  Returning failure.", fn );
		    return( -1 );
		}
		
		syslog(LOG_WARNING,"%s: poll() returned EAGAIN.  Retrying.", fn );
		sleep(5);
		continue;
	    }

	    /* anything else, we're really jacked about it. */
	    syslog(LOG_ERR, "%s: poll() failed: %s -- Returning failure.", fn, strerror( errno ) );
	    return( -1 );
	}
	
	FailCount = 0;
	

	/*
	 * PROXY LOOPS
	 */

	/*
	 * Check the server now to see if he has any data to send.
	 */
	if ( pending || fds[ SERVER ].revents )
	{
	    for ( ; ; )
	    {
		status = IMAP_Read( Server->conn, Server->ReadBuf, 
			       sizeof Server->ReadBuf );
		
		if ( status == -1 )
		{
		    if ( errno == EINTR )
			continue;
		    
		    syslog(LOG_WARNING, "%s: IMAP_Read() failed reading from IMAP server on sd [%d]: %s", fn, Server->conn->sd, strerror( errno ) );
		    return( -1 );
		}
		break;
	    }
	    
	    if ( status == 0 )
	    {
		/* the server closed the connection, dammit */
		syslog(LOG_ERR, "%s: IMAP server unexpectedly closed the connection on sd %d", fn, Server->conn->sd );
		return( -1 );
	    }
	    
	    /* whatever we read from the server, ship off to the client */
	    for ( ; ; )
	    {
		BytesSent = IMAP_Write( Client->conn, Server->ReadBuf, status );
		
		if ( BytesSent == -1 )
		{
		    if ( errno == EINTR )
			continue;
		    
		    syslog(LOG_ERR, "%s: IMAP_Write() failed sending data to client on sd [%d]: %s", fn, Client->conn->sd, strerror( errno ) );
		    return( -1 );
		}
		break;
	    }  /* end of infinite for loop for IMAP_Write() to client */
	}          /* end of if conditional -- were there server sd events? */
	
	
	/*
	 * Then we send data from the client to the server.  We have to watch
	 * this side a little bit closer...
	 */
	if ( ! fds[ CLIENT ].revents )
	{
		continue;
	}

	do
	{
		status = IMAP_Line_Read( Client );
		
		if ( status == -1 )
		{
		    syslog(LOG_NOTICE, "%s: Failed to read line from client on socket %d", fn, Client->conn->sd );
		    return( -1 );
		}
	    
		if ( Client->TraceOn )
		{
		    snprintf( TraceBuf, sizeof TraceBuf - 1, "\n\n-----> C= %d %s CLIENT: sd [%d]\n", time( 0 ), ( (TraceUser) ? TraceUser : "Null username" ), Client->conn->sd );
		    write( Tracefd, TraceBuf, strlen( TraceBuf ) );
		    write( Tracefd, Client->ReadBuf, status );
		}
		
	    
		/* this is a command -- is it logout? */
		CP = memchr(Client->ReadBuf, ' ',
			    Client->ReadBytesProcessed );
	    
		if ( Client->ReadBuf )
		{
		    CP++;
		    if ( !strncasecmp( Client->ReadBuf, "QUIT", 4 ) )
		    {
			memset( Server->ReadBuf, 0, sizeof Server->ReadBuf );
			return( 1 );
		    } else if ( !strncasecmp( Client->ReadBuf, "FORCEDQUIT", 9 ) ) {
			    BytesSent = IMAP_Write( Server->conn, "QUIT\n\r", status );
			    if ( BytesSent == -1 )
			    {
				if ( errno == EINTR )
				    continue;
				
				syslog(LOG_ERR, "%s: IMAP_Write() failed sending data to client on sd [%d]: %s", fn, Client->conn->sd, strerror( errno ) );
				return( -1 );
			    }
			    break;
		    }
		}
		
		/*
		 * it's some command other than QUIT or FORCEDQUIT ...
		 * just ship it over
		 */
		for ( ; ; )
		{
		    BytesSent = IMAP_Write( Server->conn, Client->ReadBuf, status );
		    if ( BytesSent == -1 )
		    {
			if ( errno == EINTR )
			    continue;
			
			syslog(LOG_ERR, "%s: IMAP_Write() failed sending data to client on sd [%d]: %s", fn, Client->conn->sd, strerror( errno ) );
			return( -1 );
		    }
		    break;
		}
	} while ( Client->BytesInReadBuffer > Client->ReadBytesProcessed );

	
    }
}



    




/*++
 * Function:	HandleRequest
 *
 * Purpose:	Handle incoming imap requests (as a thread)
 *
 * Parameters:	int, client socket descriptor
 *
 * Returns:	nada
 *
 * Authors:	Dave McMurtrie <davemcmurtrie@hotmail.com>
 *
 * Notes:	This function actually only handles unauthenticated
 *		traffic from an imap client.  As such it can only make sense
 *		of the following IMAP commands (rfc 2060):  CAPA, EPOP and 
 * 		QUIT.  Also, it handles the commands that are internal to
 * 		the proxy server such as  P_TRACE, P_NEWLOG, P_DUMPICC and
 *		P_RESETCOUNTERS.
 *
 *              None of these commands should ever have the need to send
 *              a boatload of data, so we avoid some error checking and
 *              undue complexity in this routine by just making sure that
 *              any given read from the client doesn't fill our read
 *              buffer.  If it does, we just drop the connection.
 *--
 */
extern void HandleRequest( int clientsd )
{
    char *fn = "HandleRequest";
    ITD_Struct Client;
    ICD_Struct conn;
    int sd;
    char *Tag;
    char *Command;
    char *Username;
    char *Password;
    char *AuthMech;
    char *Lasts;
    char *EndOfLine;
    char *CP;
    char SendBuf[BUFSIZE];
    int BytesRead;
    int rc;
    int NumRef = 0;
    unsigned int BufLen = BUFSIZE - 1;
    char S_UserName[MAXUSERNAMELEN];
    char S_Tag[MAXTAGLEN];
    char S_Password[MAXPASSWDLEN];
    unsigned char LiteralFlag;          /* flag to deal with passwords sent */
					/* as string literals */
    
    
    struct pollfd fds[1];
    nfds_t nfds;
    int PollFailCount;
    
    PollFailCount = 0;
    
    /* initialize the client ITD */
    memset( &Client, 0, sizeof( ITD_Struct ) );
    memset( &conn, 0, sizeof( ICD_Struct ) );
    Client.conn = &conn;
    Client.conn->sd = clientsd;

    /* send the banner to the client */
    if ( IMAP_Write( Client.conn, Banner, BannerLen ) == -1 )
    {
	syslog(LOG_ERR, "%s: IMAP_Write() failed: %s.  Closing client connection.", fn, strerror( errno ) );
	IMAPCount->CurrentClientConnections--;
	close( Client.conn->sd );
	return;
    }

    /* set up our poll fd structs */
    nfds = 1;
    
    fds[ 0 ].fd = Client.conn->sd;
    fds[ 0 ].events = POLLIN;
    
    /* start a command loop */
    for ( ; ; )
    {
	LiteralFlag = NON_LITERAL_PASSWORD;

	fds[ 0 ].revents = 0;
	
	rc = poll( fds, nfds, POLL_TIMEOUT );
	
	if ( !rc )
	{
	    /*
	     * our client timeout was exceeded.  Drop this connection.
	     */
	    syslog(LOG_ERR, "%s: no data received from client for %d minutes.  Closing client connection.", fn, POLL_TIMEOUT_MINUTES );
	    IMAPCount->CurrentClientConnections--;
	    close( Client.conn->sd );
	    return;
	}
	
	if ( rc < 0 )
	{
	    /* If we were interrupted by a signal, just continue the loop. */
	    if ( errno == EINTR )
	    {
		syslog(LOG_INFO, "%s: poll() was interrupted by a signal -- continuing.", fn);
		continue;
	    }
	    
	    
	    /* resource issue -- try again. */
	    if ( errno == EAGAIN )
	    {
		PollFailCount++;
		if ( PollFailCount == 5 )
		{
		    syslog(LOG_ERR, "%s: poll() returned EAGAIN.  Exceeded retry limit.  Closing client connection.", fn );
		    IMAPCount->CurrentClientConnections--;
		    close( Client.conn->sd );
		    return;
		}
		
		syslog(LOG_WARNING, "%s: poll() returned EAGAIN.  Retrying.", fn );
		sleep(5);
		continue;
	    }
	    
	    /* anything else, we're really jacked about it. */
	    syslog(LOG_ERR, "%s: poll() failed: %s -- Closing connection.", fn, strerror( errno ) );
	    IMAPCount->CurrentClientConnections--;
	    close( Client.conn->sd );
	    return;
	}
	
	PollFailCount = 0;

	BytesRead = IMAP_Line_Read( &Client );
	
	if ( BytesRead == -1 )
	{
	    IMAPCount->CurrentClientConnections--;
	    close( Client.conn->sd );
	    return;
	}
	
	if ( Client.MoreData )
	{
	    syslog( LOG_WARNING, "%s: Too much data read from unauthenticated client.  Dropping the connection.", fn );
	    IMAPCount->CurrentClientConnections--;
	    close( Client.conn->sd );
	    return;
	}
	
	
    	/* First grab the tag */

	/* Clear the SendBuffer before we send anything ! */
	memset(SendBuf, 0, sizeof(SendBuf));

	EndOfLine = Client.ReadBuf + BytesRead;

	Tag = memtok( Client.ReadBuf, EndOfLine, &Lasts );
	if ( !Tag )
		continue;

	if (( !imparse_isatom( Tag ) ) ||
	     ( Tag[0] == '*' && !Tag[1] ) )
	{
	    if ( ! strncasecmp( (const char *)Tag, "USER", 4 ) ) {
		    snprintf( SendBuf, BufLen, "-ERR Zu wenig Angaben für den USER Befehl.\r\n" );
	    } else if ( ! strncasecmp( (const char *)Tag, "PASS", 4 ) ) {
		    snprintf( SendBuf, BufLen, "-ERR Zu wenig Angaben f\xfcr den PASS Befehl.\r\n" );
	    } else {
		    snprintf( SendBuf, BufLen, "-ERR Unbekannter Befehl: %s\r\n", Tag);
	    }
	    if ( IMAP_Write( Client.conn, SendBuf, strlen(SendBuf) ) == -1 )
	    {
		IMAPCount->CurrentClientConnections--;
		close( Client.conn->sd );
		return;
	    }
	    continue;
	}
	
	Command = memtok( NULL, EndOfLine, &Lasts );
	if ( !Command )
	{
	    /* Tag with no command */
	    if ( ! strncasecmp( (const char *)Tag, "USER", 4 ) ) {
		    snprintf( SendBuf, BufLen, "-ERR Zu wenig Angaben f\xfcr den USER Befehl.\r\n" );
	    } else if ( ! strncasecmp( (const char *)Tag, "PASS", 4 )) {
		    snprintf( SendBuf, BufLen, "-ERR Zu wenig Angaben f\xfcr den PASS Befehl.\r\n" );
	    } else if ( ! strncasecmp( (const char *)Tag, "CAPA", 4)) {
		    /* send the capability list to the client */
		    if ( IMAP_Write( Client.conn, Capability, CapabilityLen) == -1 )
		    {
			syslog(LOG_ERR, "%s: IMAP_Write() failed: %s.  Closing client connection.", fn, strerror( errno ) );
			IMAPCount->CurrentClientConnections--;
			close( Client.conn->sd );
			return;
		    }
	    } else if ( ! strncasecmp( (const char *)Tag, "EPOP", 4)) {
		    /* send the capability list to the client */
		    if ( IMAP_Write( Client.conn, Epoplist, EpoplistLen ) == -1 )
		    {
			syslog(LOG_ERR, "%s: IMAP_Write() failed: %s.  Closing client connection.", fn, strerror( errno ) );
			IMAPCount->CurrentClientConnections--;
			close( Client.conn->sd );
			return;
		    }
	    } else if (! strncasecmp( (const char *)Tag, "QUIT", 4)) {

		/*
		 * We caught a LOGOUT from the client.  Respond with
		 * a successful logout back to the client.
		 */
		cmd_logout( &Client, Tag );
	    	/* 
	     	* close the client side socket.
	     	*/
	    	IMAPCount->CurrentClientConnections--;
	    	close( Client.conn->sd );
	    	return;
	    } else 
		    snprintf( SendBuf, BufLen, "ERR Unknown command\r\n" );

	    if ( IMAP_Write( Client.conn, SendBuf, strlen(SendBuf) ) == -1 )
	    {
		IMAPCount->CurrentClientConnections--;
		close( Client.conn->sd );
		return;
	    }
	    continue;
	}
	
	/*
	 * We should have a valid tag and command now.  React as
	 * appropriate...
	 */
	strncpy( S_Tag, Tag, MAXTAGLEN - 1 );
	S_Tag[MAXTAGLEN - 1] = '\0';
	if ( ! strcasecmp( (const char *)Tag, "USER" ) )
	{
	    /*
	     * Got a LOGIN command.  validate that we got all four required
	     * tokens (Tag, Command, Username, Password) before we waste
	     * a connection to the IMAP server.
	     */
	    Username = Command;
	    if ( !Username )
	    {
		/* no username -- complain back to the client */
		snprintf( SendBuf, BufLen, "%s BAD Missing required argument to USER\r\n", Tag );
		if ( IMAP_Write( Client.conn, SendBuf, strlen(SendBuf) ) == -1 )
		{
		    IMAPCount->CurrentClientConnections--;
		    close( Client.conn->sd );
		    return;
		}
		continue;
	    }
	    snprintf( SendBuf, BufLen, "+OK Password required for %s\r\n", Username);
	    if ( IMAP_Write( Client.conn, SendBuf, strlen(SendBuf) ) == -1 )
            {
		    IMAPCount->CurrentClientConnections--;
		    close( Client.conn->sd );
		    return;
            }
	    strncpy( S_UserName, Username, sizeof S_UserName - 1 );	    
	    S_UserName[sizeof S_UserName - 1] = '\0';
        }
	else if ( ! strncasecmp( (const char *)Tag, "PASS", 4 ) )
	{
	    /*
	     * Got a LOGIN command.  validate that we got all four required
	     * tokens (Tag, Command, Username, Password) before we waste
	     * a connection to the IMAP server.
	     */
	    Password = Command;
	    strncpy( S_Password, Password, sizeof S_UserName - 1 );	    
	    S_Password[sizeof S_Password - 1] = '\0';
	    
	    rc = cmd_login( &Client, S_UserName, S_Password, sizeof S_Password, S_Tag, LiteralFlag );

	    if ( rc == 0)
		continue;
	    
	    if ( rc == 1)
	    {
		/*
		 * We caught a LOGOUT from the client.  Respond with
		 * a successful logout back to the client.
		 */
		Tag = memtok( Client.ReadBuf, EndOfLine, &Lasts );
		if ( Tag )
		{
		    strncpy( S_Tag, Tag, MAXTAGLEN - 1 );
		    S_Tag[MAXTAGLEN - 1] = '\0';
		    cmd_logout( &Client, S_Tag );
		}
	    }
	    
	    /* 
	     * close the client side socket.
	     */

	    IMAPCount->CurrentClientConnections--;
	    close( Client.conn->sd );
	    return;
	} else {
	    /*
	     * We got a command that we don't understand.  Treat this the
	     * same way the cyrus implementation does -- tell the client to
	     * log in first.
	     */
	    snprintf( SendBuf, BufLen, "-ERR Bad command %s, please login first\r\n", Tag, Command );
	    if ( IMAP_Write( Client.conn, SendBuf, strlen(SendBuf) ) == -1 )
	    {
		IMAPCount->CurrentClientConnections--;
		close( Client.conn->sd );
		return;
	    }
	    continue;
	    
	}
    }  /* End of infinite for loop */
    
    
    /* should never reach this code */
    IMAPCount->CurrentClientConnections--;
    close( Client.conn->sd );
    return;
}

/*
 *                            _________
 *                           /        |
 *                          /         |
 *                         /    ______|
 *                        /    /       ________
 *                       |    |        |      /
 *                       |    |        |_____/
 *                       |    |        ______
 *                       |    |        |     \
 *                       |    |        |______\
 *                        \    \_______
 *                         \           |
 *                          \          |
 *                           \_________|
 */


syntax highlighted by Code2HTML, v. 0.9.1