/*
** 
**               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:
**
**	main.c
**
**  Abstract:
**
**	The main source module for the IMAP proxy server.  This source
**	module contains routines to handle server initialization
**	and the main server loop.
**
**  Authors:
**
**	Dave McMurtrie <davemcmurtrie@hotmail.com>
**
**  RCS:
**
**	$Source: /afs/pitt.edu/usr12/dgm/work/IMAP_Proxy/src/RCS/main.c,v $
**	$Id: main.c,v 1.17 2003/10/10 15:07:02 dgm Exp $
**      
**  Modification History:
**
**	$Log: main.c,v $
**	Revision 1.17  2003/10/10 15:07:02  dgm
**	Patch by Ken Murchison <ken@oceana.com> applied.  Discard "SASL"
**	capability if advertised by the server.  This is a new extension
**	that will be supported in Cyrus v2.2.2.
**
**	Revision 1.16  2003/10/09 13:03:52  dgm
**	Added configurable tcp keepalive support.  Added retry logic for
**	initial socket connection to imap server in SetBannerAndCapability()
**	so it won't just fatally exit on ECONNREFUSED (submitted by Gary
**	Mills <mills@cc.UManitoba.CA>).
**
**	Revision 1.15  2003/07/14 16:39:58  dgm
**	Applied patch by William Yodlowsky <wyodlows@andromeda.rutgers.edu>
**	to allow TLS to work on machines without /dev/random.
**
**	Applied patch by Gary Windham <windhamg@email.arizona.edu> to add
**	tcp wrappers support.
**
**	Revision 1.14  2003/05/20 19:04:23  dgm
**	Comment changes only.
**
**	Revision 1.13  2003/05/15 11:34:34  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 14:19:00  dgm
**	Changed all uses of AF_INET constant to PF_INET.
**
**	Revision 1.11  2003/05/13 11:40:16  dgm
**	Patches by Ken Murchison <ken@oceana.com> to clean up build process.
**
**	Revision 1.10  2003/05/06 12:12:21  dgm
**	Applied patches by Ken Murchison <ken@oceana.com> to include SSL
**	support.
**
**	Revision 1.9  2003/04/16 12:15:52  dgm
**	Added support for syslog configuration.
**	Removed a few ifdef LINUXs by always storing tcp service port as
**	network short.
**
**	Revision 1.8  2003/02/20 12:55:03  dgm
**	SetBannerAndCapability() now checks to see if the server supports
**	UNSELECT and sets a flag in the global proxy config struct.
**
**	Revision 1.7  2003/02/19 13:01:40  dgm
**	Changes to SetBannerAndCapability() to strip out unsupported AUTH=
**	mechanisms from the capability string.
**
**	Revision 1.6  2003/01/27 13:58:18  dgm
**	patch by Frode Nordahl <frode@powertech.no> to allow
**	compilation on Linux platforms.
**
**	Revision 1.5  2002/12/17 14:25:14  dgm
**	Added support for global configuration structure.
**	Modified supported command line arguments.
**	Minor bugfixes from Gary Mills incorporated.
**
**	Revision 1.4  2002/09/06 13:33:05  dgm
**	Added code to ignore SIGPIPE and SIGHUP.
**
**	Revision 1.3  2002/08/30 17:08:35  dgm
**	oops.  forgot to initialize the trace mutex...
**
**	Revision 1.2  2002/08/28 15:56:24  dgm
**	replaced all internal logging calls with standard syslog calls.
**	Changed call to setrlimit such that it's now done dynamically,
**	based on the max number of connections we want to allow.
**
**	Revision 1.1  2002/07/03 12:07:51  dgm
**	Initial revision
**
**
*/


static char *rcsId = "$Id: main.c,v 1.17 2003/10/10 15:07:02 dgm Exp $";
static char *rcsSource = "$Source: /afs/pitt.edu/usr12/dgm/work/IMAP_Proxy/src/RCS/main.c,v $";
static char *rcsAuthor = "$Author: dgm $";

#define _REENTRANT

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <string.h>
#include <errno.h>
#include <netdb.h>
#include "common.h"
#include "pop3proxy.h"
#include <pthread.h>
#include <sys/resource.h>
#include <sys/mman.h>
#include <pwd.h>
#include <syslog.h>
#include <signal.h>

#if HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif

#ifdef HAVE_LIBWRAP
#include <tcpd.h>
#endif

/*
 * Global variables.  Many of these things are global just as an optimization.
 * For example, there's no reason to have to do a hostname lookup every
 * single time we want to connect to the imap server.  We do it once and 
 * store it globally.
 */
char Banner[BUFSIZE];                /* banner line returned from IMAP svr */
unsigned int BannerLen;
char Capability[BUFSIZE];            /* IMAP capability line from server */
unsigned int CapabilityLen;
char Epoplist[BUFSIZE];
unsigned int EpoplistLen;
ISD_Struct ISD;                      /* global imap server descriptor */
ICC_Struct *ICC_free;                /* ICC free listhead */
ICC_Struct *ICC_HashTable[ HASH_TABLE_SIZE ];
IMAPCounter_Struct *IMAPCount;       /* global imap counter struct */
pthread_mutex_t mp;                  /* "main" mutex used for ICC sync */
pthread_mutex_t trace;               /* mutex used for username tracing */
char TraceUser[MAXUSERNAMELEN];      /* username we want to trace */
int Tracefd;                         /* fd of our trace file (always open) */
ProxyConfig_Struct PC_Struct;        /* Global configuration data */

#if HAVE_LIBSSL
#if USE_IMAP
SSL_CTX *tls_ctx;
static int verify_depth = 5;
static int verify_error = X509_V_OK;

static int verify_callback( int, X509_STORE_CTX *);
static int set_cert_stuff( SSL_CTX *, const char *, const char * );
#endif
#endif

#ifdef HAVE_LIBWRAP
int allow_severity = LOG_DEBUG;
int deny_severity = LOG_ERR;
char *service;
#endif

/*
 * Internal Prototypes
 */
static int SetBannerAndCapability( void );
static void ServerInit( void );
static void Usage( void );



int main( int argc, char *argv[] )
{
    char *fn = "main()";
    char f_randfile[ PATH_MAX ];
    int listensd;                      /* socket descriptor we'll bind to */
    int clientsd;                      /* incoming socket descriptor */
    int addrlen;                       
    struct sockaddr_in srvaddr;
    struct sockaddr_in cliaddr;
    pthread_t ThreadId;                /* thread id of each incoming conn */
    pthread_t RecycleThread;           /* used just for the recycle thread */
    pthread_attr_t attr;               /* generic thread attribute struct */
    int rc, i, fd;
    pid_t pid;                         /* used just for a fork call */
    struct linger lingerstruct;        /* for the socket reuse stuff */
    int flag;                          /* for the socket reuse stuff */
    ICC_Struct *ICC_tptr;             
    extern char *optarg;
    extern int optind;
    char ConfigFile[ MAXPATHLEN ];     /* path to our config file */
#ifdef HAVE_LIBWRAP
    struct request_info r;             /* request struct for libwrap */
#endif

    flag = 1;
    ConfigFile[0] = '\0';

    /*
     * Ignore signals we don't want to die from but we don't care enough
     * about to catch.
     */
    signal( SIGPIPE, SIG_IGN );
    signal( SIGHUP, SIG_IGN );
    

    while (( i = getopt( argc, argv, "f:h" ) ) != EOF )
    {
	switch( i )
	{
	case 'f':
	    /* user specified a config filename */
	    strncpy( ConfigFile, optarg, sizeof ConfigFile -1 );
	    syslog( LOG_INFO, "%s: Using configuration file '%s'",
		    fn, ConfigFile );
	    break;
	    
	case 'h':
	    Usage();
	    exit( 0 );

	case '?':
	    Usage();
	    exit( 1 );
	}
    }


    /* 
     * Make sure we know which config file to use and then set our config
     * options.
     */
    if ( ! ConfigFile[0] )
    {
	strncpy( ConfigFile, DEFAULT_CONFIG_FILE, sizeof ConfigFile -1 );
	syslog( LOG_INFO, "%s: Using default configuration file '%s'.",
		fn, ConfigFile );
    }
    
    SetConfigOptions( ConfigFile );
    SetLogOptions();
    
#ifdef HAVE_LIBWRAP
    /*
     * Set our tcpd service name
     */
    if (service = strrchr(argv[0], '/'))
	    service++;
    else
	    service = argv[0];
#endif

    /*
     * Initialize some stuff.
     */
    rc = pthread_mutex_init(&mp, NULL);
    if ( rc )
    {
	syslog(LOG_ERR, "%s: pthread_mutex_init() returned error [%d] initializing main mutex.  Exiting.", fn, rc );
	exit( 1 );
    }

    rc = pthread_mutex_init(&trace, NULL);
    if ( rc )
    {
	syslog(LOG_ERR, "%s: pthread_mutex_init() returned error [%d] initializing trace mutex.  Exiting.", fn, rc );
	exit( 1 );
    }

    TraceUser[0] = '\0';
    
    syslog( LOG_INFO, "%s: Allocating %d IMAP connection structures.", 
	    fn, PC_Struct.cache_size );

    ICC_free = malloc( ( sizeof ( ICC_Struct ) ) 
		       * PC_Struct.cache_size );
    
    if ( ! ICC_free )
    {
	syslog(LOG_ERR, "%s: malloc() failed to allocate [%d] IMAPConnectionContext structures: %s", fn, PC_Struct.cache_size, strerror( errno ) );
	exit( 1 );
    }
    
    memset( ICC_free, 0, sizeof ( ICC_Struct ) * PC_Struct.cache_size );
    
    ICC_tptr = ICC_free;

    /*
     * Bug fixed by Gary Mills <mills@cc.UManitoba.CA>.  I was pre-incrementing
     * ICC_tptr and then assigning.  I guess gcc evaluates the expression
     * incorrectly, since I never had a problem with this.  Gary had the
     * problem with cc, so it's fixed here.
     */
    for ( i = 0; i < PC_Struct.cache_size - 1; i++ )
    {
	ICC_tptr->next = ICC_tptr + 1;
	ICC_tptr++;
    }
    
    memset( ICC_HashTable, 0, sizeof ICC_HashTable );

    ServerInit();

    /* detach from our parent if necessary */
    if (! (getppid() == 1) )
    {
	if ( (pid = fork()) < 0)
	{
	    syslog(LOG_ERR, "%s: initial call to fork() failed: %s", fn, strerror(errno));
	    exit( 1 );
	}
	else if ( pid > 0)
	{
	    exit( 0 );
	}
	
	if (setsid() == -1)
	{
	    syslog(LOG_WARNING, "%s: setsid() failed: %s", 
		   fn, strerror(errno));
	}
	if ( (pid = fork()) < 0)
	{
	    syslog(LOG_ERR, "%s: secondary call to fork() failed: %s", fn, 
		   strerror(errno));
	    exit( 1 );
	}
	else if ( pid > 0)
	{
	    exit( 0 );
	}
    }

    
    if ( SetBannerAndCapability() )
    {
	syslog(LOG_ERR, "%s: Failed to get banner string and capability from imap server.  Exiting.", fn);
	exit( 1 );
    }

    if ( PC_Struct.login_disabled )
    {
#if HAVE_LIBSSL
#if USE_IMAP
	if ( PC_Struct.support_starttls )
	{
	    /* Initialize SSL_CTX */
	    SSL_library_init();
	    
            /* Need to seed PRNG, too! */
            if ( RAND_egd( RAND_file_name( f_randfile, sizeof( f_randfile ) ) ) < 0) 
	    {
                /* Not an EGD, so read and write it. */
                if ( RAND_load_file( f_randfile, -1 ) )
                    RAND_write_file( f_randfile );
            }
	    
	    SSL_load_error_strings();
	    tls_ctx = SSL_CTX_new( TLSv1_client_method() );
	    if ( tls_ctx == NULL )
	    {
		syslog(LOG_ERR, "%s: Failed to create new SSL_CTX.  Exiting.", fn);
		exit( 1 );
	    }

	    /* Work around all known bugs */
	    SSL_CTX_set_options( tls_ctx, SSL_OP_ALL );

	    if ( ! SSL_CTX_load_verify_locations( tls_ctx,
						  PC_Struct.tls_ca_file,
						  PC_Struct.tls_ca_path ) ||
		 ! SSL_CTX_set_default_verify_paths( tls_ctx ) )
	    {
		syslog(LOG_ERR, "%s: Failed to load CA data.  Exiting.", fn);
		exit( 1 );
	    }

	    if ( ! set_cert_stuff( tls_ctx,
				   PC_Struct.tls_cert_file,
				   PC_Struct.tls_key_file ) )
	    {
		syslog(LOG_ERR, "%s: Failed to load cert/key data.  Exiting.", fn);
		exit( 1 );
	    }

	    SSL_CTX_set_verify(tls_ctx, SSL_VERIFY_NONE, verify_callback);
	}
	else
#endif /* USE_IMAP */
#endif /* HAVE_LIBSSL */
	{
	    /* We're screwed!  We won't be able to login without SASL */
	    syslog(LOG_ERR, "%s: IMAP server has LOGINDISABLED and we can't do STARTTLS.  Exiting.", fn);
	    exit( 1 );
	}
    }
    


    memset( (char *) &srvaddr, 0, sizeof srvaddr );
    srvaddr.sin_family = PF_INET;
    srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    syslog(LOG_ERR, "%s: Binding to tcp port %d", fn, PC_Struct.listen_port );
    srvaddr.sin_port = htons(PC_Struct.listen_port);

    listensd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if ( listensd == -1 )
    {
	syslog(LOG_ERR, "%s: socket() failed: %s", fn, strerror(errno));
	exit( 1 );
    }

    setsockopt(listensd, SOL_SOCKET, SO_REUSEADDR, (void *)&flag, 
	       sizeof(flag));
    lingerstruct.l_onoff = 1;
    lingerstruct.l_linger = 5;
    setsockopt(listensd, SOL_SOCKET, SO_LINGER, (void *)&lingerstruct, 
	       sizeof(lingerstruct));

    if ( PC_Struct.send_tcp_keepalives )
    {
	lingerstruct.l_onoff = 1;
	syslog( LOG_INFO, "%s: Enabling SO_KEEPALIVE.", fn );
	setsockopt( listensd, SOL_SOCKET, SO_KEEPALIVE, (void *)&lingerstruct.l_onoff, sizeof lingerstruct.l_onoff );
    }

	    
    if ( bind(listensd, (struct sockaddr *)&srvaddr, sizeof( srvaddr ) ) < 0 )
    {
	syslog(LOG_ERR, "%s: bind() failed: %s", fn, strerror(errno) );
	exit( 1 );
    }

    /*
     * Create and mmap() our stat file while we're still root.  Since it's
     * configurable, we want to make sure we do this as root so there's the
     * greatest possibility that we'll have permission to write where we
     * need to.
     */
    syslog( LOG_INFO, "%s: Using global statistics file '%s'", fn,
	    PC_Struct.stat_filename );
    
    fd = open( PC_Struct.stat_filename, O_RDWR | O_CREAT, S_IREAD | S_IWRITE );
    if ( fd == -1 )
    {
	syslog(LOG_ERR, "%s: open() failed for '%s': %s -- Exiting.", fn, 
	       PC_Struct.stat_filename, strerror( errno ) );
	exit( 1 );
    }
    
    if ( ( ftruncate( fd, sizeof( IMAPCounter_Struct ) ) ) == -1 )
    {
	syslog(LOG_ERR, "%s: ftruncate() failed: %s -- Exiting.", 
	       fn, strerror( errno ) );
	exit( 1 );
    }
    
    IMAPCount = ( IMAPCounter_Struct *)mmap( 0, sizeof( IMAPCounter_Struct ), 
		    PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 );
    
    if ( IMAPCount == MAP_FAILED )
    {
	syslog(LOG_ERR, "%s: mmap() failed: %s -- Exiting.", 
	       fn, strerror( errno ) );
	exit( 1 );
    }
    
    memset( IMAPCount, 0, sizeof( IMAPCounter_Struct ) );
    IMAPCount->StartTime = time( 0 );
    IMAPCount->CountTime = time( 0 );

    if ( BecomeNonRoot() )
	exit( 1 );

    /* some misc thread setup */
    rc = pthread_attr_init( &attr );
    if ( rc )
    {
	syslog(LOG_ERR, "%s: pthread_attr_init() failed: [%d]\n", fn, rc);
	exit( 1 );
    }
    
    rc = pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED );
    if ( rc )
    {
	syslog(LOG_ERR, "%s: pthread_attr_setdetachstate() failed: [%d]\n", 
	       fn, rc);
	exit( 1 );
    }

    /* launch a recycle thread before we loop */
    pthread_create( &RecycleThread, &attr, (void *)ICC_Recycle_Loop, NULL );

    syslog(LOG_INFO, "%s: Launched ICC recycle thread with id %d", 
	   fn, RecycleThread );

    /*
     * Now start listening and accepting connections.
     */
    if ( listen(listensd, MAX_CONN_BACKLOG) < 0)
    {
	syslog( LOG_ERR, "%s: listen() failed: %s -- Exiting", 
	       fn, strerror(errno));
	exit( 1 );
    }

    syslog( LOG_INFO, "%s: Normal server startup.", fn );

    /*
     * Main server loop
     */
    for ( ;; )
    {
	/*
	 * Bug fixed by Gary Mills <mills@cc.UManitoba.CA>.  I forgot
	 * to initialize addrlen.
	 */
	addrlen = sizeof cliaddr;
	clientsd = accept( listensd, (struct sockaddr *)&cliaddr, &addrlen );
	if ( clientsd == -1 )
	{
	    syslog(LOG_WARNING, "%s: accept() failed: %s -- retrying", 
		   fn, strerror(errno));
	    sleep( 1 );
	    continue;
	}

#ifdef HAVE_LIBWRAP
	request_init(&r, RQ_DAEMON, service, 0);
	request_set(&r, RQ_FILE, clientsd, 0);
	sock_host(&r);
	if (!hosts_access(&r))
	{
	    shutdown(clientsd, SHUT_RDWR);
	    close(clientsd);
	    syslog(deny_severity, "refused connection from %s", eval_client(&r));
	    continue;
	}
#endif

	IMAPCount->TotalClientConnectionsAccepted++;
	IMAPCount->CurrentClientConnections++;
	
	if ( IMAPCount->CurrentClientConnections > 
	     IMAPCount->PeakClientConnections )
	    IMAPCount->PeakClientConnections = IMAPCount->CurrentClientConnections;
	
	pthread_create( &ThreadId, &attr, (void *)HandleRequest, (void *)clientsd );
	
    }
}

	
    
    
	
/*
 * Function definitions.
 */



/*++
 * Function:	Usage
 *
 * Purpose:	Display a usage string to stdout
 *
 * Parameters:	None.
 *
 * Returns:	nada
 * 
 * Authors:	Dave McMurtrie <davemcmurtrie@hotmail.com>
 * 
 * Notes:
 *--
 */
void Usage( void )
{
    printf("Usage: %s [-f config filename] [-h]\n", PGM );
    return;
}



/*++
 * Function:	ServerInit
 *
 * Purpose:	Initialize some stuff.  Set some global variables.
 *
 * Parameters:	none
 *
 * Returns:	nada -- exits on error
 * 
 * Authors:	Dave McMurtrie <davemcmurtrie@hotmail.com>
 * 
 * Notes:       relies on global copy of ProxyConfig_Struct "PC_Struct"
 *--
 */
static void ServerInit( void ) 
{
    char *fn = "ServerInit()";
    struct hostent *hp;
    struct servent *sp;
    struct rlimit rl;
    int rc;
    struct passwd *pw;

    
    /* open the global trace file and make proc_username own it */
    syslog( LOG_INFO, "%s: Using '%s' for global protocol logging file.",
	    fn, PC_Struct.protocol_log_filename );
    
    Tracefd = open( PC_Struct.protocol_log_filename,
		    O_RDWR | O_CREAT | O_TRUNC, 0600 );
    
    if ( Tracefd == -1 )
    {
	syslog(LOG_ERR, "%s: open() failed for '%s': %s -- Exiting.", fn,
	       PC_Struct.protocol_log_filename, strerror( errno ) );
	exit( 1 );
    }

    if ( ( pw = getpwnam( PC_Struct.proc_username ) ) == NULL )
    {
	syslog(LOG_ERR, "%s: getpwnam() failed for user '%s' -- Exiting.", fn, 
	       PC_Struct.proc_username );
	exit( 1 );
    }
    
    rc = chown( PC_Struct.protocol_log_filename, pw->pw_uid, pw->pw_gid );
    
    if ( rc )
    {
	syslog(LOG_ERR, "%s: Failed to set ownership of file '%s' to '%s': %s -- Exiting.", fn, PC_Struct.protocol_log_filename, PC_Struct.proc_username, strerror( errno ) );
	exit( 1 );
    }
    
    /* 
     * increase the number of open file descriptors we're allowed.  Base
     * This number on the number of simultaneous connections we allow.
     * Also allow stdin, stdout, stderr and a few misc pipes and doors.
     */
    rl.rlim_cur = ( PC_Struct.cache_size * 2 ) + 10;
    rl.rlim_max = ( PC_Struct.cache_size * 2 ) + 10;

    rc = setrlimit( RLIMIT_NOFILE, &rl );

    if ( rc )
    {
	syslog(LOG_ERR, "%s: setrlimit() failed to set max number of open file descriptors to %d: %s", fn, ( PC_Struct.cache_size * 2 + 10), strerror( errno ) );
	exit(1);
    }
    
    
    /* grab a host entry for the imap server. */
    syslog( LOG_INFO, "%s: proxying to POP3 server '%s'.", fn, 
	    PC_Struct.server_hostname );
    
    hp = gethostbyname( PC_Struct.server_hostname );
    
    if ( !hp )
    {
	syslog(LOG_ERR, "%s: gethostbyname() failed to resolve hostname of remote IMAP server: %s", fn, strerror(errno) );
	exit(1);
    }

    memcpy( &ISD.host, hp, sizeof(struct hostent) );
    
    syslog(LOG_INFO, "%s: proxying to POP3 port %d", 
	   fn, PC_Struct.server_port );
    ISD.serv.s_port = htons(PC_Struct.server_port);
        
    /* 
     * fill in the address family, the host address, and the
     * service port of our global socket address structure
     */
    ISD.srv.sin_family = PF_INET;
    memcpy( &ISD.srv.sin_addr.s_addr, ISD.host.h_addr, ISD.host.h_length );
    ISD.srv.sin_port = ISD.serv.s_port;
}





/*++
 * Function:	SetBannerAndCapability
 *
 * Purpose:	Connect to an IMAP server as a client and fetch the initial
 *		banner string and the output from a CAPABILITY command.
 *
 * Parameters:	none
 *
 * Returns:	0 on success
 *              -1 on failure
 *
 * Authors:	Dave McMurtrie <davemcmurtrie@hotmail.com>
 *
 * Notes:       All AUTH mechanisms will be stripped from the capability
 *              string.  AUTH=LOGIN will be added.
 *              The support_unselect flag in the global copy of the
 *              ProxyConfig struct will be set in this function depending on
 *              whether the server supports UNSELECT or not.
 *--
 */
static int SetBannerAndCapability( void )
{
    int sd;
    ITD_Struct itd;
    ICD_Struct conn;
    int BytesRead;
    int Processed;
    char *fn = "SetBannerAndCapability()";
    char *CP;
    int NumRef = 0;

    /* initialize some stuff */
    memset( &itd, 0, sizeof itd );

    for ( ;; )
    {
	sd = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
	if ( sd == -1 )
	{
	    syslog(LOG_ERR, "%s: socket() failed: %s", fn, strerror(errno) );
	    return( -1 );
	}
	
	if ( connect( sd, (struct sockaddr *)&ISD.srv, sizeof(ISD.srv) ) == -1 )
	{
	    syslog(LOG_ERR, "%s: connect() to imap server on socket [%d] failed: %s", fn, sd, strerror(errno));
	    close( sd );
	    
	    if ( errno == ECONNREFUSED && ++NumRef < 10 )
	    {
		sleep( 60 );    /* POP3 server may not be started yet. */
		continue;
	    }
	    return( -1 );
	}
	break;  /* Success */
    }

    
    memset( &conn, 0, sizeof ( ICD_Struct ) );
    itd.conn = &conn;
    itd.conn->sd = sd;
    
    /*
     * The first thing we get back from the server should be the
     * banner string.
     */
    BytesRead = IMAP_Line_Read( &itd );
    if ( BytesRead == -1 )
    {
	close( itd.conn->sd );
	return( -1 );
    }
    
    
    if ( sizeof Banner < BytesRead )
    {
	syslog(LOG_ERR, "%s: Storing %d byte banner string from POP3 server would cause buffer overflow.", fn, BytesRead );
	close( itd.conn->sd );
	return( -1 );
    }
    
    memcpy( Banner, itd.ReadBufline, BytesRead );
    BannerLen = BytesRead;
	
	      
    /*
     * See if the string we got back starts with "+OK" by comparing the
     * first 4 characters of the buffer.
     */
    if ( strncasecmp( Banner, IMAP_UNTAGGED_OK, strlen(IMAP_UNTAGGED_OK)) )
    {
	syslog(LOG_ERR, "%s: Unexpected response from imap server on initial connection: %s", fn, Banner);
	close( itd.conn->sd );
	return( -1 );
    }

    /*
     * Then get the capability list if available
     */
    if (IMAP_Write( itd.conn, "CAPA\r\n", strlen("CAPA\r\n")) == -1) {
	syslog(LOG_ERR, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );	
	close( itd.conn->sd );
	return( -1 );
    }

    Capability[0] = '\0';
    for (;;) {
	BytesRead = IMAP_Line_Read( &itd );
	if ( BytesRead == -1 )
		continue;
	strlcat(Capability, itd.ReadBufline, sizeof(Capability));
	if (!memcmp(itd.ReadBufline, "+OK", 3))
    		strlcat(Capability, "PROXY\r\n", sizeof(Capability));
	if (!memcmp(itd.ReadBufline, ".\r\n", 3))
		break;
    }
    CapabilityLen = strlen(Capability);

    /*
     * Get the epop list if available
     */
    if (IMAP_Write( itd.conn, "EPOP\r\n", strlen("EPOP\r\n")) == -1) {
	syslog(LOG_ERR, "%s: IMAP_Write() failed: %s", fn, strerror(errno) );	
	close( itd.conn->sd );
	return( -1 );
    }

    Epoplist[0] = '\0';
    for (;;) {
	BytesRead = IMAP_Line_Read( &itd );
	if ( BytesRead == -1 )
		continue;
	strlcat(Epoplist, itd.ReadBufline, sizeof(Epoplist));
	if (!memcmp(itd.ReadBuf, "-ERR", 4))
		break;
	if (!memcmp(itd.ReadBuf, ".\r\n", 3))
		break;
    }
    EpoplistLen = strlen(Epoplist);

    /* Be nice and logout */
    if ( IMAP_Write( itd.conn, "QUIT\r\n", strlen("QUIT\r\n") ) == -1 )
    {
	syslog(LOG_WARNING, "%s: POP3_Write() failed on LOGOUT: %s -- Returning success anyway.", fn, strerror(errno) );
	return( 0 );
    }
    
    /* read the final OK logout */
    BytesRead = IMAP_Line_Read( &itd );
    if ( BytesRead == -1 )
    {
	syslog(LOG_WARNING, "%s: IMAP_Line_Read() failed on LOGOUT.  Returning success anyway.", fn );
    }
    
    close( itd.conn->sd );
    return( 0 );
}
    

#if HAVE_LIBSSL
#if USE_IMAP
/* taken from OpenSSL apps/s_cb.c */

static int verify_callback(int ok, X509_STORE_CTX * ctx)
{
    char    buf[256];
    X509   *err_cert;
    int     err;
    int     depth;

    syslog(LOG_ERR,"Doing a peer verify");

    err_cert = X509_STORE_CTX_get_current_cert(ctx);
    err = X509_STORE_CTX_get_error(ctx);
    depth = X509_STORE_CTX_get_error_depth(ctx);

    X509_NAME_oneline(X509_get_subject_name(err_cert), buf, sizeof(buf));
    if (ok==0)
    {
      syslog(LOG_ERR, "verify error:num=%d:%s", err,
	     X509_verify_cert_error_string(err));
      
	if (verify_depth >= depth) {
	    ok = 0;
	    verify_error = X509_V_OK;
	} else {
	    ok = 0;
	    verify_error = X509_V_ERR_CERT_CHAIN_TOO_LONG;
	}
    }
    switch (ctx->error) {
    case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
	X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), buf, sizeof(buf));
	syslog(LOG_NOTICE, "issuer= %s", buf);
	break;
    case X509_V_ERR_CERT_NOT_YET_VALID:
    case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
	syslog(LOG_NOTICE, "cert not yet valid");
	break;
    case X509_V_ERR_CERT_HAS_EXPIRED:
    case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
	syslog(LOG_NOTICE, "cert has expired");
	break;
    }

    return (ok);
}


static int set_cert_stuff(SSL_CTX * ctx,
			  const char *cert_file, const char *key_file)
{
    if (cert_file != NULL) {
	if (SSL_CTX_use_certificate_file(ctx, cert_file,
					 SSL_FILETYPE_PEM) <= 0) {
	    syslog(LOG_ERR, "unable to get certificate from '%s'", cert_file);
	    return (0);
	}
	if (key_file == NULL)
	    key_file = cert_file;
	if (SSL_CTX_use_PrivateKey_file(ctx, key_file,
					SSL_FILETYPE_PEM) <= 0) {
	    syslog(LOG_ERR, "unable to get private key from '%s'", key_file);
	    return (0);
	}
	/* Now we know that a key and cert have been set against
         * the SSL context */
	if (!SSL_CTX_check_private_key(ctx)) {
	    syslog(LOG_ERR,
		   "Private key does not match the certificate public key");
	    return (0);
	}
    }
    return (1);
}
#endif /* USE_IMAP */
#endif /* HAVE_LIBSSL */


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








syntax highlighted by Code2HTML, v. 0.9.1